FELIX-2217 Enhance OBR support to accomodate both the "old" OSGi OBR API and the "new" Apache Felix OBR API

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@926131 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 64c97d3..d1886d9 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -92,18 +92,18 @@
                             org.osgi.service.http,
                             org.apache.felix.scr;
                             org.apache.felix.shell;
-                            org.apache.felix.bundlerepository;
                             org.osgi.service.*;resolution:=optional,
                             javax.portlet;resolution:=optional,
                             javax.servlet.*;version=2.4,
                             *
                         </Import-Package>
+                        <DynamicImport-Package>
+                            org.apache.felix.bundlerepository,
+                            org.osgi.service.obr
+                        </DynamicImport-Package>
                         <Embed-Dependency>
                             <!-- Import/Export-Package parsing -->
-                            org.apache.felix.bundlerepository;
-                                inline=org/apache/felix/bundlerepository/impl/R4*.class|
-                                    org/apache/felix/bundlerepository/impl/Util.class|
-                                    org/apache/felix/bundlerepository/impl/VersionRange.class,
+                            org.apache.felix.utils;inline=org/apache/felix/utils/manifest/**,
                             
                             <!-- ServiceTracker -->
                             org.osgi.compendium;
@@ -166,10 +166,7 @@
                                         <_donotcopy>LICENSE.json</_donotcopy>
                                         <!-- <_donotcopy>(LICENSE.json|NOTICE.bare)</_donotcopy> -->
                                         <Embed-Dependency>
-                                            org.apache.felix.bundlerepository;
-                                                inline=org/apache/felix/bundlerepository/impl/R4*.class|
-                                                    org/apache/felix/bundlerepository/impl/Util.class|
-                                                    org/apache/felix/bundlerepository/impl/VersionRange.class
+                                            org.apache.felix.utils;inline=org/apache/felix/utils/manifest/**
                                         </Embed-Dependency>
                                     </instructions>
                                 </configuration>
@@ -236,11 +233,26 @@
         <!--  Parsing Import/Export-Package headers -->
         <dependency>
             <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.bundlerepository</artifactId>
-            <version>1.5.0-SNAPSHOT</version>
-            <scope>compile</scope>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
             <optional>true</optional>
         </dependency>
-
+        
+        <!-- OSGi and Apache Felix OBR API -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.service.obr</artifactId>
+            <version>1.0.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+            <version>1.5.0-SNAPSHOT</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+        
     </dependencies>
 </project>
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateHelper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateHelper.java
index 1b47bd7..bba71e0 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateHelper.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateHelper.java
@@ -22,16 +22,16 @@
 import java.io.File;
 import java.io.InputStream;
 
-import org.apache.felix.bundlerepository.RepositoryAdmin;
-import org.apache.felix.bundlerepository.Resolver;
-import org.apache.felix.bundlerepository.Resource;
+import org.apache.felix.bundlerepository.Reason;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
-import org.apache.felix.webconsole.internal.obr.DeployerThread;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.log.LogService;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resolver;
+import org.osgi.service.obr.Resource;
 
 
 class UpdateHelper extends BaseUpdateInstallHelper
@@ -82,8 +82,14 @@
             throw new BundleException( "Cannot update bundle: Symbolic Name is required for OBR update" );
         }
 
-        // try updating from OBR
-        if ( updateFromOBR() )
+        // try updating from Apache Felix OBR
+        if ( updateFromFelixOBR() )
+        {
+            return bundle;
+        }
+
+        // try updating from OSGi OBR
+        if ( updateFromOsgiOBR() )
         {
             return bundle;
         }
@@ -114,12 +120,71 @@
     }
 
 
-    private boolean updateFromOBR() throws InvalidSyntaxException
+    private boolean updateFromFelixOBR()
     {
-        RepositoryAdmin ra = ( RepositoryAdmin ) getService( RepositoryAdmin.class.getName() );
+        org.apache.felix.bundlerepository.RepositoryAdmin ra = ( org.apache.felix.bundlerepository.RepositoryAdmin ) getService( "org.apache.felix.bundlerepository.RepositoryAdmin" );
         if ( ra != null )
         {
-            getLog().log( LogService.LOG_DEBUG, "Trying to update from OSGi Bundle Repository" );
+            getLog().log( LogService.LOG_DEBUG, "Trying to update from OSGi Bundle Repository (Apache Felix API)" );
+
+            final org.apache.felix.bundlerepository.Resolver resolver = ra.resolver();
+
+            String version = ( String ) bundle.getHeaders().get( Constants.BUNDLE_VERSION );
+            if ( version == null )
+            {
+                version = "0.0.0";
+            }
+            final String filter = "(&(symbolicname=" + bundle.getSymbolicName() + ")(!(version=" + version
+                + "))(version>=" + version + "))";
+            final org.apache.felix.bundlerepository.Requirement req = ra.getHelper().requirement(
+                bundle.getSymbolicName(), filter );
+            final org.apache.felix.bundlerepository.Resource[] resources = ra
+                .discoverResources( new org.apache.felix.bundlerepository.Requirement[]
+                    { req } );
+            final org.apache.felix.bundlerepository.Resource resource = selectHighestVersion( resources );
+            if ( resource != null )
+            {
+                resolver.add( resource );
+
+                if ( !resolver.resolve() )
+                {
+                    logRequirements( "Cannot updated bundle from OBR due to unsatisfied requirements", resolver
+                        .getUnsatisfiedRequirements() );
+                }
+                else
+                {
+                    logResource( "Installing Requested Resources", resolver.getAddedResources() );
+                    logResource( "Installing Required Resources", resolver.getRequiredResources() );
+                    logResource( "Installing Optional Resources", resolver.getOptionalResources() );
+
+                    // deploy the resolved bundles and ensure they are started
+                    resolver.deploy( org.apache.felix.bundlerepository.Resolver.START );
+                    getLog().log( LogService.LOG_INFO, "Bundle updated from OSGi Bundle Repository" );
+
+                    return true;
+                }
+            }
+            else
+            {
+                getLog().log( LogService.LOG_INFO,
+                "Nothing to update, OSGi Bundle Repository does not provide more recent version" );
+            }
+        }
+        else
+        {
+            getLog().log( LogService.LOG_DEBUG, "Cannot updated from OSGi Bundle Repository: Service not available" );
+        }
+
+        // fallback to false, nothing done
+        return false;
+    }
+
+    private boolean updateFromOsgiOBR()
+    {
+        RepositoryAdmin ra = ( RepositoryAdmin ) getService( "org.osgi.service.obr.RepositoryAdmin" );
+        if ( ra != null )
+        {
+            getLog().log( LogService.LOG_DEBUG, "Trying to update from OSGi Bundle Repository (OSGi API)" );
 
             final Resolver resolver = ra.resolver();
 
@@ -139,21 +204,17 @@
 
                 if ( !resolver.resolve() )
                 {
-                    DeployerThread.logRequirements( getLog(),
-                        "Cannot updated bundle from OBR due to unsatisfied requirements", resolver
-                            .getUnsatisfiedRequirements() );
+                    logRequirements( "Cannot updated bundle from OBR due to unsatisfied requirements", resolver
+                        .getUnsatisfiedRequirements() );
                 }
                 else
                 {
-                    DeployerThread.logResource( getLog(), "Installing Requested Resources", resolver
-                        .getAddedResources() );
-                    DeployerThread.logResource( getLog(), "Installing Required Resources", resolver
-                        .getRequiredResources() );
-                    DeployerThread.logResource( getLog(), "Installing Optional Resources", resolver
-                        .getOptionalResources() );
+                    logResource( "Installing Requested Resources", resolver.getAddedResources() );
+                    logResource( "Installing Required Resources", resolver.getRequiredResources() );
+                    logResource( "Installing Optional Resources", resolver.getOptionalResources() );
 
                     // deploy the resolved bundles and ensure they are started
-                    resolver.deploy( Resolver.START );
+                    resolver.deploy( true );
                     getLog().log( LogService.LOG_INFO, "Bundle updated from OSGi Bundle Repository" );
 
                     return true;
@@ -175,6 +236,67 @@
     }
 
 
+    //---------- Apache Felix OBR API helper
+
+    private org.apache.felix.bundlerepository.Resource selectHighestVersion(
+        final org.apache.felix.bundlerepository.Resource[] candidates )
+    {
+        if ( candidates == null || candidates.length == 0 )
+        {
+            // nothing to do if there are none
+            return null;
+        }
+        else if ( candidates.length == 1 )
+        {
+            // simple choice if there is a single one
+            return candidates[0];
+        }
+
+        // now go on looking for the highest version
+        org.apache.felix.bundlerepository.Resource best = candidates[0];
+        for ( int i = 1; i < candidates.length; i++ )
+        {
+            if ( best.getVersion().compareTo( candidates[i].getVersion() ) < 0 )
+            {
+                best = candidates[i];
+            }
+        }
+        return best;
+    }
+
+
+    private void logResource( String message, org.apache.felix.bundlerepository.Resource[] res )
+    {
+        if ( res != null && res.length > 0 )
+        {
+            getLog().log( LogService.LOG_INFO, message );
+            for ( int i = 0; i < res.length; i++ )
+            {
+                getLog().log( LogService.LOG_INFO,
+                    "  " + i + ": " + res[i].getSymbolicName() + ", " + res[i].getVersion() );
+            }
+        }
+    }
+
+
+    private void logRequirements( String message, Reason[] reasons )
+    {
+        getLog().log( LogService.LOG_ERROR, message );
+        for ( int i = 0; reasons != null && i < reasons.length; i++ )
+        {
+            String moreInfo = reasons[i].getRequirement().getComment();
+            if ( moreInfo == null )
+            {
+                moreInfo = reasons[i].getRequirement().getFilter().toString();
+            }
+            getLog().log( LogService.LOG_ERROR,
+                "  " + i + ": " + reasons[i].getRequirement().getName() + " (" + moreInfo + ")" );
+        }
+    }
+
+
+    //---------- OSGi OBR API helper
+
     private Resource selectHighestVersion( final Resource[] candidates )
     {
         if ( candidates == null || candidates.length == 0 )
@@ -199,4 +321,33 @@
         }
         return best;
     }
+
+
+    private void logResource( String message, Resource[] res )
+    {
+        if ( res != null && res.length > 0 )
+        {
+            getLog().log( LogService.LOG_INFO, message );
+            for ( int i = 0; i < res.length; i++ )
+            {
+                getLog().log( LogService.LOG_INFO,
+                    "  " + i + ": " + res[i].getSymbolicName() + ", " + res[i].getVersion() );
+            }
+        }
+    }
+
+
+    private void logRequirements( String message, Requirement[] reasons )
+    {
+        getLog().log( LogService.LOG_ERROR, message );
+        for ( int i = 0; reasons != null && i < reasons.length; i++ )
+        {
+            String moreInfo = reasons[i].getComment();
+            if ( moreInfo == null )
+            {
+                moreInfo = reasons[i].getFilter().toString();
+            }
+            getLog().log( LogService.LOG_ERROR, "  " + i + ": " + reasons[i].getName() + " (" + moreInfo + ")" );
+        }
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractBundleRepositoryRenderHelper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..af686cd
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractBundleRepositoryRenderHelper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.obr;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+abstract class AbstractBundleRepositoryRenderHelper
+{
+
+    protected final AbstractWebConsolePlugin logger;
+
+    private final ServiceTracker repositoryAdmin;
+
+
+    protected AbstractBundleRepositoryRenderHelper( final AbstractWebConsolePlugin logger,
+        final BundleContext bundleContext, final String serviceName )
+    {
+        this.logger = logger;
+        this.repositoryAdmin = new ServiceTracker( bundleContext, serviceName, null );
+        this.repositoryAdmin.open();
+    }
+
+
+    void dispose()
+    {
+        repositoryAdmin.close();
+    }
+
+
+    boolean hasRepositoryAdmin()
+    {
+        return getRepositoryAdmin() != null;
+    }
+
+
+    protected final Object getRepositoryAdmin()
+    {
+        return repositoryAdmin.getService();
+    }
+
+
+    abstract void doDeploy( String[] bundles, boolean start, boolean optional );
+
+
+    abstract void doAction( String action, String urlParam ) throws IOException, ServletException;
+
+
+    abstract String getData( final String filter, final boolean details, final Bundle[] bundles );
+
+}
\ No newline at end of file
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 324c90b..4206b6d 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
@@ -18,28 +18,19 @@
  */
 package org.apache.felix.webconsole.internal.obr;
 
+
 import java.io.IOException;
 import java.util.Enumeration;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.bundlerepository.Capability;
-import org.apache.felix.bundlerepository.Reason;
-import org.apache.felix.bundlerepository.Requirement;
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
-import org.apache.felix.bundlerepository.Repository;
-import org.apache.felix.bundlerepository.RepositoryAdmin;
-import org.apache.felix.bundlerepository.Resolver;
-import org.apache.felix.bundlerepository.Resource;
-import org.osgi.framework.InvalidSyntaxException;
+
 
 /**
  * This class provides a plugin for rendering the available OSGi Bundle Repositories
@@ -49,368 +40,212 @@
 {
     private static final String LABEL = "obr";
     private static final String TITLE = "OSGi Repository";
-    private static final String[] CSS = { "/res/ui/obr.css" };
-
-    // Define a constant of that name to prevent NoClassDefFoundError in
-    // updateFromOBR trying to load the class with RepositoryAdmin.class
-    private static final String REPOSITORY_ADMIN_NAME = RepositoryAdmin.class.getName();
+    private static final String[] CSS =
+        { "/res/ui/obr.css" };
 
     // templates
     private final String TEMPLATE;
 
+    private AbstractBundleRepositoryRenderHelper helper;
+
+
     /**
      *
      */
     public BundleRepositoryRender()
     {
-        super(LABEL, TITLE, CSS);
+        super( LABEL, TITLE, CSS );
 
         // load templates
-        TEMPLATE = readTemplateFile("/templates/obr.html");
+        TEMPLATE = readTemplateFile( "/templates/obr.html" );
     }
 
+
+    public void deactivate()
+    {
+        if ( helper != null )
+        {
+            helper.dispose();
+            helper = null;
+        }
+
+        super.deactivate();
+    }
+
+
     /**
      * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
         String query = request.getQueryString();
-        if (query == null || query.length() == 0)
+        if ( query == null || query.length() == 0 )
         {
-            response.sendRedirect(LABEL + "?list=a");
+            response.sendRedirect( LABEL + "?list=a" );
             return;
         }
         // prepare variables
-        DefaultVariableResolver vars = ((DefaultVariableResolver) WebConsoleUtil.getVariableResolver(request));
-        vars.put("__data__", getData(request));
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        vars.put( "__data__", getData( request ) );
 
-        response.getWriter().print(TEMPLATE);
+        response.getWriter().print( TEMPLATE );
     }
 
+
     /**
      * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
-    protected void doPost(HttpServletRequest request, HttpServletResponse response)
-        throws ServletException, IOException
+    protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
     {
-        final RepositoryAdmin admin = getRepositoryAdmin();
-
-        if (admin == null)
+        if ( !hasRepositoryAdmin() )
         {
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
-                "RepositoryAdmin service is missing");
+            response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "RepositoryAdmin service is missing" );
             return;
         }
 
-        final String action = request.getParameter("action");
-        final String deploy = request.getParameter("deploy");
-        final String deploystart = request.getParameter("deploystart");
-        final String optional = request.getParameter("optional");
+        final String action = request.getParameter( "action" );
+        final String deploy = request.getParameter( "deploy" );
+        final String deploystart = request.getParameter( "deploystart" );
+        final String optional = request.getParameter( "optional" );
 
-        if (action != null)
+        if ( action != null )
         {
-            doAction(action, request.getParameter("url"), admin);
-            response.getWriter().print(getData(request));
+            doAction( action, request.getParameter( "url" ) );
+            response.getWriter().print( getData( request ) );
             return;
         }
 
-        if (deploy != null || deploystart != null)
+        if ( deploy != null || deploystart != null )
         {
-            doDeploy(request.getParameterValues("bundle"), deploystart != null, optional != null, admin);
-            doGet(request, response);
+            doDeploy( request.getParameterValues( "bundle" ), deploystart != null, optional != null );
+            doGet( request, response );
             return;
         }
 
-        super.doPost(request, response);
+        super.doPost( request, response );
     }
 
-    private final RepositoryAdmin getRepositoryAdmin()
+
+    AbstractBundleRepositoryRenderHelper getHelper()
     {
-        try
-        {
-            return ( RepositoryAdmin ) super.getService( REPOSITORY_ADMIN_NAME );
-        }
-        catch (Throwable t)
-        {
-            log("Cannot create RepositoryAdmin service tracker", t);
-            return null;
-        }
-    }
-
-    private final String getData(HttpServletRequest request)
-    {
-        final Bundle[] bundles = getBundleContext().getBundles();
-        try
-        {
-            RepositoryAdmin admin = getRepositoryAdmin();
-            Resource[] resources = null;
-            boolean details = request.getParameter("details") != null;
-            if (admin != null)
-            {
-                String list = request.getParameter("list");
-                String query = request.getParameter("query");
-                if (list != null)
-                {
-                    String filter;
-                    if ("-".equals(list))
-                    {
-                        StringBuffer sb = new StringBuffer("(!(|");
-                        for (int c = 0; c < 26; c++)
-                        {
-                            sb.append("(presentationname=").append((char) ('a' + c))
-                                    .append("*)(presentationname=")
-                                    .append((char)('A' + c)).append("*)");
-                        }
-                        sb.append("))");
-                        filter = sb.toString();
-                    }
-                    else
-                    {
-                        filter = "(|(presentationname=" + list.toLowerCase() + "*)(presentationname=" + list.toUpperCase() + "*))";
-                    }
-                    resources = admin.discoverResources(filter);
-                }
-                else if (query != null)
-                {
-                    if (query.indexOf('=') > 0)
-                    {
-                        resources = admin.discoverResources(new Requirement[] { parseRequirement(admin, query) });
-                    }
-                    else
-                    {
-                        resources = admin.discoverResources("(|(presentationame=*" + query + "*)(symbolicname=*" + query + "*))");
-                    }
-                }
-                else
-                {
-                    StringBuffer sb = new StringBuffer("(&");
-                    for (Enumeration e = request.getParameterNames(); e.hasMoreElements();)
-                    {
-                        String k = (String) e.nextElement();
-                        String v = request.getParameter(k);
-                        if (v != null && v.length() > 0
-                                && !"details".equals(k) && !"deploy".equals(k)
-                                && !"deploystart".equals(k) && !"bundle".equals(k)
-                                && !"optional".equals(k))
-                        {
-                            sb.append("(").append(k).append("=").append(v).append(")");
-                        }
-                    }
-                    sb.append(")");
-                    resources = admin.discoverResources(sb.toString());
-
-                }
-            }
-            JSONObject json = new JSONObject();
-            json.put("status", admin != null);
-            if (admin != null)
-            {
-                final Repository repositories[] = admin.listRepositories();
-                for (int i = 0; repositories != null && i < repositories.length; i++)
-                {
-                    json.append("repositories", new JSONObject()
-                            .put("lastModified", repositories[i].getLastModified())
-                            .put("name", repositories[i].getName())
-                            .put("url", repositories[i].getURI()));
-                }
-            }
-            for (int i = 0; resources != null && i < resources.length; i++)
-            {
-                json.append("resources", toJSON(resources[i], bundles, details));
-            }
-            return json.toString();
-        }
-        catch (InvalidSyntaxException e)
-        {
-            log("Failed to parse filter.", e);
-            return "";
-        }
-        catch (JSONException e)
-        {
-            log("Failed to serialize repository to JSON object.", e);
-            return "";
-        }
-    }
-
-    private Requirement parseRequirement(RepositoryAdmin admin, String req) throws InvalidSyntaxException {
-        int p = req.indexOf(':');
-        String name;
-        String filter;
-        if (p > 0) {
-            name = req.substring(0, p);
-            filter = req.substring(p + 1);
-        } else {
-            if (req.contains("package")) {
-                name = "package";
-            } else if (req.contains("service")) {
-                name = "service";
-            } else {
-                name = "bundle";
-            }
-            filter = req;
-        }
-        if (!filter.startsWith("(")) {
-            filter = "(" + filter + ")";
-        }
-        return admin.requirement(name, filter);
-    }
-
-    private final void doAction(String action, String urlParam, RepositoryAdmin admin)
-        throws IOException, ServletException
-    {
-        Repository[] repos = admin.listRepositories();
-        Repository repo = getRepository(repos, urlParam);
-
-        String uri = repo != null ? repo.getURI() : urlParam;
-
-        if ("delete".equals(action))
-        {
-            if (!admin.removeRepository(uri))
-            {
-                throw new ServletException("Failed to remove repository with URL " + uri);
-            }
-        }
-        else if ("add".equals(action) || "refresh".equals(action))
+        if ( helper == null )
         {
             try
             {
-                admin.addRepository(uri);
+                helper = new FelixBundleRepositoryRenderHelper( this, getBundleContext() );
             }
-            catch (IOException e)
+            catch ( Throwable felixt )
             {
-                throw e;
-            }
-            catch (Exception e)
-            {
-                throw new ServletException("Failed to " + action + " repository " + uri
-                    + ": " + e.toString());
-            }
+                // ClassNotFoundException, ClassDefNotFoundError
 
-        }
-    }
-
-    private final void doDeploy(String[] bundles, boolean start, boolean optional, RepositoryAdmin repoAdmin)
-    {
-        try
-        {
-            // check whether we have to do something
-            if (bundles == null || bundles.length == 0)
-            {
-                log("No resources to deploy");
-                return;
-            }
-
-            Resolver resolver = repoAdmin.resolver();
-
-            // prepare the deployment
-            for (int i = 0; i < bundles.length; i++)
-            {
-                String bundle = bundles[i];
-                if (bundle == null || bundle.equals("-"))
+                try
                 {
-                    continue;
+                    helper = new OsgiBundleRepositoryRenderHelper( this, getBundleContext() );
                 }
-
-                String filter = "(id=" + bundle + ")";
-                Resource[] resources = repoAdmin.discoverResources(filter);
-                if (resources != null && resources.length > 0)
+                catch ( Throwable osgit )
                 {
-                    resolver.add(resources[0]);
+                    // ClassNotFoundException, ClassDefNotFoundError
                 }
             }
-
-            DeployerThread dt = new DeployerThread( resolver, this, start, optional );
-            dt.start();
         }
-        catch (InvalidSyntaxException e)
+
+        return helper;
+    }
+
+
+    private boolean hasRepositoryAdmin()
+    {
+        AbstractBundleRepositoryRenderHelper helper = getHelper();
+        return helper != null && helper.hasRepositoryAdmin();
+    }
+
+
+    private String getData( final HttpServletRequest request )
+    {
+        AbstractBundleRepositoryRenderHelper helper = getHelper();
+        if ( helper == null || !helper.hasRepositoryAdmin() )
         {
-            throw new IllegalStateException(e);
+            return "";
+        }
+
+        boolean details = request.getParameter( "details" ) != null;
+
+        final String filter;
+        String list = WebConsoleUtil.urlDecode( request.getParameter( "list" ) );
+        String query = WebConsoleUtil.urlDecode( request.getParameter( "query" ) );
+        if ( list != null )
+        {
+            if ( "-".equals( list ) )
+            {
+                StringBuffer sb = new StringBuffer( "(!(|" );
+                for ( int c = 0; c < 26; c++ )
+                {
+                    sb.append( "(presentationname=" ).append( ( char ) ( 'a' + c ) ).append( "*)(presentationname=" )
+                        .append( ( char ) ( 'A' + c ) ).append( "*)" );
+                }
+                sb.append( "))" );
+                filter = sb.toString();
+            }
+            else
+            {
+                filter = "(|(presentationname=" + list.toLowerCase() + "*)(presentationname=" + list.toUpperCase()
+                    + "*))";
+            }
+        }
+        else if ( query != null )
+        {
+            if ( query.indexOf( '=' ) > 0 )
+            {
+                if (query.startsWith( "(" )) {
+                    filter = query;
+                } else {
+                    filter = "(" + query + ")";
+                }
+            }
+            else
+            {
+                filter = "(|(presentationame=*" + query + "*)(symbolicname=*" + query + "*))";
+            }
+        }
+        else
+        {
+            StringBuffer sb = new StringBuffer( "(&" );
+            for ( Enumeration e = request.getParameterNames(); e.hasMoreElements(); )
+            {
+                String k = ( String ) e.nextElement();
+                String v = request.getParameter( k );
+                if ( v != null && v.length() > 0 && !"details".equals( k ) && !"deploy".equals( k )
+                    && !"deploystart".equals( k ) && !"bundle".equals( k ) && !"optional".equals( k ) )
+                {
+                    sb.append( "(" ).append( k ).append( "=" ).append( v ).append( ")" );
+                }
+            }
+            sb.append( ")" );
+            filter = sb.toString();
+        }
+
+        return helper.getData( filter, details, getBundleContext().getBundles() );
+    }
+
+
+    private void doAction( String action, String urlParam ) throws IOException, ServletException
+    {
+        AbstractBundleRepositoryRenderHelper helper = getHelper();
+        if ( helper != null )
+        {
+            helper.doAction( action, urlParam );
         }
     }
 
-    private final Repository getRepository(Repository[] repos, String repositoryUrl)
+
+    private void doDeploy( String[] bundles, boolean start, boolean optional )
     {
-        if (repositoryUrl == null || repositoryUrl.length() == 0)
+        AbstractBundleRepositoryRenderHelper helper = getHelper();
+        if ( helper != null )
         {
-            return null;
+            helper.doDeploy( bundles, start, optional );
         }
-
-        for (int i = 0; i < repos.length; i++)
-        {
-            if (repositoryUrl.equals(repos[i].getURI()))
-            {
-                return repos[i];
-            }
-        }
-
-        return null;
-    }
-
-    private final JSONObject toJSON(Resource resource, Bundle[] bundles, boolean details)
-        throws JSONException
-    {
-        final String symbolicName = resource.getSymbolicName();
-        final String version = resource.getVersion().toString();
-        boolean installed = false;
-        for (int i = 0; symbolicName != null && !installed && bundles != null
-            && i < bundles.length; i++)
-        {
-            final String ver = (String) bundles[i].getHeaders("").get(
-                Constants.BUNDLE_VERSION);
-            installed = symbolicName.equals(bundles[i].getSymbolicName())
-                && version.equals(ver);
-        }
-        JSONObject json = new JSONObject(resource.getProperties()) //
-        .put("id", resource.getId()) //
-        .put("presentationname", resource.getPresentationName()) //
-        .put("symbolicname", symbolicName) //
-        .put("url", resource.getURI()) //
-        .put("version", version) //
-        .put("categories", resource.getCategories()) //
-        .put("installed", installed);
-
-        if (details)
-        {
-            Capability[] caps = resource.getCapabilities();
-            for (int i = 0; caps != null && i < caps.length; i++)
-            {
-                json.append("capabilities", new JSONObject().
-                    put("name", caps[i].getName()).
-                    put("properties", new JSONObject(caps[i].getProperties())));
-            }
-            Requirement[] reqs = resource.getRequirements();
-            for (int i = 0; reqs != null && i < reqs.length; i++)
-            {
-                json.append("requirements", new JSONObject().
-                    put("name", reqs[i].getName()).
-                    put("filter", reqs[i].getFilter()).
-                    put("optional", reqs[i].isOptional()));
-            }
-
-            final RepositoryAdmin admin = getRepositoryAdmin();
-            Resolver resolver = admin.resolver();
-            resolver.add(resource);
-            resolver.resolve(Resolver.NO_OPTIONAL_RESOURCES);
-            Resource[] required = resolver.getRequiredResources();
-            for (int i = 0; required != null && i < required.length; i++)
-            {
-                json.append("required", toJSON(required[i], bundles, false));
-            }
-            Resource[] optional = resolver.getOptionalResources();
-            for (int i = 0; optional != null && i < optional.length; i++)
-            {
-                json.append("optional", toJSON(optional[i], bundles, false));
-            }
-            Reason[] unsatisfied = resolver.getUnsatisfiedRequirements();
-            for (int i = 0; unsatisfied != null && i < unsatisfied.length; i++)
-            {
-                json.append("unsatisfied", new JSONObject().
-                    put("name", unsatisfied[i].getRequirement().getName()).
-                    put("filter", unsatisfied[i].getRequirement().getFilter()).
-                    put("optional", unsatisfied[i].getRequirement().isOptional()));
-            }
-        }
-        return json;
     }
 
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixBundleRepositoryRenderHelper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..0ba2eeb
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixBundleRepositoryRenderHelper.java
@@ -0,0 +1,247 @@
+/*
+ * 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.obr;
+
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import org.apache.felix.bundlerepository.Capability;
+import org.apache.felix.bundlerepository.Reason;
+import org.apache.felix.bundlerepository.Repository;
+import org.apache.felix.bundlerepository.RepositoryAdmin;
+import org.apache.felix.bundlerepository.Requirement;
+import org.apache.felix.bundlerepository.Resolver;
+import org.apache.felix.bundlerepository.Resource;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+
+
+/**
+ * This class provides a plugin for rendering the available OSGi Bundle Repositories
+ * and the resources they provide.
+ */
+public class FelixBundleRepositoryRenderHelper extends AbstractBundleRepositoryRenderHelper
+{
+
+    public FelixBundleRepositoryRenderHelper( AbstractWebConsolePlugin logger, BundleContext bundleContext )
+    {
+        super( logger, bundleContext, RepositoryAdmin.class.getName() );
+    }
+
+
+    String getData( final String filter, final boolean details, Bundle[] bundles )
+    {
+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+        if ( admin != null )
+        {
+            try
+            {
+                JSONObject json = new JSONObject();
+                json.put( "status", admin != null );
+                if ( admin != null )
+                {
+                    final Repository repositories[] = admin.listRepositories();
+                    for ( int i = 0; repositories != null && i < repositories.length; i++ )
+                    {
+                        json.append( "repositories", new JSONObject().put( "lastModified",
+                            repositories[i].getLastModified() ).put( "name", repositories[i].getName() ).put( "url",
+                            repositories[i].getURI() ) );
+                    }
+                }
+
+                Resource[] resources = admin.discoverResources( filter );
+                for ( int i = 0; resources != null && i < resources.length; i++ )
+                {
+                    json.append( "resources", toJSON( resources[i], bundles, details ) );
+                }
+
+                return json.toString();
+            }
+            catch ( InvalidSyntaxException e )
+            {
+                logger.log( "Failed to parse filter.", e );
+            }
+            catch ( JSONException e )
+            {
+                logger.log( "Failed to serialize repository to JSON object.", e );
+            }
+        }
+
+        // fall back to no data
+        return "";
+    }
+
+
+    final void doAction( String action, String urlParam ) throws IOException, ServletException
+    {
+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+        Repository[] repos = admin.listRepositories();
+        Repository repo = getRepository( repos, urlParam );
+
+        String uri = repo != null ? repo.getURI() : urlParam;
+
+        if ( "delete".equals( action ) )
+        {
+            if ( !admin.removeRepository( uri ) )
+            {
+                throw new ServletException( "Failed to remove repository with URL " + uri );
+            }
+        }
+        else if ( "add".equals( action ) || "refresh".equals( action ) )
+        {
+            try
+            {
+                admin.addRepository( uri );
+            }
+            catch ( IOException e )
+            {
+                throw e;
+            }
+            catch ( Exception e )
+            {
+                throw new ServletException( "Failed to " + action + " repository " + uri + ": " + e.toString() );
+            }
+
+        }
+    }
+
+
+    final void doDeploy( String[] bundles, boolean start, boolean optional )
+    {
+        try
+        {
+            // check whether we have to do something
+            if ( bundles == null || bundles.length == 0 )
+            {
+                logger.log( "No resources to deploy" );
+                return;
+            }
+
+            RepositoryAdmin repoAdmin = ( RepositoryAdmin ) getRepositoryAdmin();
+            Resolver resolver = repoAdmin.resolver();
+
+            // prepare the deployment
+            for ( int i = 0; i < bundles.length; i++ )
+            {
+                String bundle = bundles[i];
+                if ( bundle == null || bundle.equals( "-" ) )
+                {
+                    continue;
+                }
+
+                String filter = "(id=" + bundle + ")";
+                Resource[] resources = repoAdmin.discoverResources( filter );
+                if ( resources != null && resources.length > 0 )
+                {
+                    resolver.add( resources[0] );
+                }
+            }
+
+            FelixDeployer.deploy( resolver, logger, start, optional );
+        }
+        catch ( InvalidSyntaxException e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+
+    private final Repository getRepository( Repository[] repos, String repositoryUrl )
+    {
+        if ( repositoryUrl == null || repositoryUrl.length() == 0 )
+        {
+            return null;
+        }
+
+        for ( int i = 0; i < repos.length; i++ )
+        {
+            if ( repositoryUrl.equals( repos[i].getURI() ) )
+            {
+                return repos[i];
+            }
+        }
+
+        return null;
+    }
+
+
+    private final JSONObject toJSON( Resource resource, Bundle[] bundles, boolean details ) throws JSONException
+    {
+        final String symbolicName = resource.getSymbolicName();
+        final String version = resource.getVersion().toString();
+        boolean installed = false;
+        for ( int i = 0; symbolicName != null && !installed && bundles != null && i < bundles.length; i++ )
+        {
+            final String ver = ( String ) bundles[i].getHeaders( "" ).get( Constants.BUNDLE_VERSION );
+            installed = symbolicName.equals( bundles[i].getSymbolicName() ) && version.equals( ver );
+        }
+        JSONObject json = new JSONObject( resource.getProperties() ) //
+            .put( "id", resource.getId() ) //
+            .put( "presentationname", resource.getPresentationName() ) //
+            .put( "symbolicname", symbolicName ) //
+            .put( "url", resource.getURI() ) //
+            .put( "version", version ) //
+            .put( "categories", resource.getCategories() ) //
+            .put( "installed", installed );
+
+        if ( details )
+        {
+            Capability[] caps = resource.getCapabilities();
+            for ( int i = 0; caps != null && i < caps.length; i++ )
+            {
+                json.append( "capabilities", new JSONObject().put( "name", caps[i].getName() ).put( "properties",
+                    new JSONObject( caps[i].getProperties() ) ) );
+            }
+            Requirement[] reqs = resource.getRequirements();
+            for ( int i = 0; reqs != null && i < reqs.length; i++ )
+            {
+                json.append( "requirements", new JSONObject().put( "name", reqs[i].getName() ).put( "filter",
+                    reqs[i].getFilter() ).put( "optional", reqs[i].isOptional() ) );
+            }
+
+            final RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+            Resolver resolver = admin.resolver();
+            resolver.add( resource );
+            resolver.resolve( Resolver.NO_OPTIONAL_RESOURCES );
+            Resource[] required = resolver.getRequiredResources();
+            for ( int i = 0; required != null && i < required.length; i++ )
+            {
+                json.append( "required", toJSON( required[i], bundles, false ) );
+            }
+            Resource[] optional = resolver.getOptionalResources();
+            for ( int i = 0; optional != null && i < optional.length; i++ )
+            {
+                json.append( "optional", toJSON( optional[i], bundles, false ) );
+            }
+            Reason[] unsatisfied = resolver.getUnsatisfiedRequirements();
+            for ( int i = 0; unsatisfied != null && i < unsatisfied.length; i++ )
+            {
+                json.append( "unsatisfied", new JSONObject().put( "name", unsatisfied[i].getRequirement().getName() )
+                    .put( "filter", unsatisfied[i].getRequirement().getFilter() ).put( "optional",
+                        unsatisfied[i].getRequirement().isOptional() ) );
+            }
+        }
+        return json;
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixDeployer.java
similarity index 72%
rename from webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java
rename to webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixDeployer.java
index d814bb8..b7c7329 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/FelixDeployer.java
@@ -26,7 +26,7 @@
 import org.osgi.service.log.LogService;
 
 
-public class DeployerThread extends Thread
+class FelixDeployer implements Runnable
 {
 
     private final Resolver obrResolver;
@@ -37,25 +37,23 @@
 
     private final boolean optionalDependencies;
 
-
-    public DeployerThread( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
-        boolean optionalDependencies )
+    static void deploy(Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
+        boolean optionalDependencies)
     {
-        this( obrResolver, logger, startBundles, optionalDependencies, "OBR Bundle Deployer" );
+        final FelixDeployer d = new FelixDeployer(obrResolver, logger, startBundles, optionalDependencies);
+        final Thread t = new Thread(d, "OBR Bundle Deployer (Apache Felix API)");
+        t.start();
     }
 
-
-    public DeployerThread( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
-        boolean optionalDependencies, String name )
+    private FelixDeployer(Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
+        boolean optionalDependencies)
     {
-        super( name );
         this.obrResolver = obrResolver;
         this.logger = logger;
         this.startBundles = startBundles;
         this.optionalDependencies = optionalDependencies;
     }
 
-
     public void run()
     {
         int flags = 0;
@@ -66,15 +64,15 @@
             if ( obrResolver.resolve( flags ) )
             {
 
-                logResource( logger, "Installing Requested Resources", obrResolver.getAddedResources() );
-                logResource( logger, "Installing Required Resources", obrResolver.getRequiredResources() );
-                logResource( logger, "Installing Optional Resources", obrResolver.getOptionalResources() );
+                logResource( "Installing Requested Resources", obrResolver.getAddedResources() );
+                logResource( "Installing Required Resources", obrResolver.getRequiredResources() );
+                logResource( "Installing Optional Resources", obrResolver.getOptionalResources() );
 
                 obrResolver.deploy( flags );
             }
             else
             {
-                logRequirements( logger, "Cannot Install requested bundles due to unsatisfied requirements",
+                logRequirements( "Cannot Install requested bundles due to unsatisfied requirements",
                     obrResolver.getUnsatisfiedRequirements() );
             }
         }
@@ -85,7 +83,7 @@
     }
 
 
-    public static void logResource( AbstractWebConsolePlugin logger, String message, Resource[] res )
+    private void logResource( String message, Resource[] res )
     {
         if ( res != null && res.length > 0 )
         {
@@ -99,7 +97,7 @@
     }
 
 
-    public static void logRequirements( AbstractWebConsolePlugin logger, String message, Reason[] reasons )
+    private void logRequirements( String message, Reason[] reasons )
     {
         logger.log( LogService.LOG_ERROR, message );
         for ( int i = 0; reasons != null && i < reasons.length; i++ )
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiBundleRepositoryRenderHelper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..6dbe992
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiBundleRepositoryRenderHelper.java
@@ -0,0 +1,254 @@
+/*
+ * 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.obr;
+
+
+import java.io.IOException;
+import java.net.URL;
+import javax.servlet.ServletException;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.obr.Capability;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resolver;
+import org.osgi.service.obr.Resource;
+
+
+/**
+ * This class provides a plugin for rendering the available OSGi Bundle Repositories
+ * and the resources they provide.
+ */
+public class OsgiBundleRepositoryRenderHelper extends AbstractBundleRepositoryRenderHelper
+{
+    public OsgiBundleRepositoryRenderHelper( final AbstractWebConsolePlugin logger, final BundleContext bundleContext )
+    {
+        super( logger, bundleContext, RepositoryAdmin.class.getName() );
+    }
+
+
+    String getData( final String filter, final boolean details, Bundle[] bundles )
+    {
+        try
+        {
+            RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+            if ( admin == null )
+            {
+                return "";
+            }
+
+            JSONObject json = new JSONObject();
+            json.put( "status", admin != null );
+            if ( admin != null )
+            {
+                final Repository repositories[] = admin.listRepositories();
+                for ( int i = 0; repositories != null && i < repositories.length; i++ )
+                {
+                    json.append( "repositories", new JSONObject().put( "lastModified",
+                        repositories[i].getLastModified() ).put( "name", repositories[i].getName() ).put( "url",
+                        repositories[i].getURL() ) );
+                }
+            }
+
+            Resource[] resources = admin.discoverResources( filter );
+            for ( int i = 0; resources != null && i < resources.length; i++ )
+            {
+                json.append( "resources", toJSON( resources[i], bundles, details ) );
+            }
+
+            return json.toString();
+        }
+        catch ( JSONException e )
+        {
+            logger.log( "Failed to serialize repository to JSON object.", e );
+            return "";
+        }
+    }
+
+
+    private String parseRequirement( String req )
+    {
+        int p = req.indexOf( ':' );
+        String filter;
+        if ( p > 0 )
+        {
+            filter = req.substring( p + 1 );
+        }
+        else
+        {
+            filter = req;
+        }
+        if ( !filter.startsWith( "(" ) )
+        {
+            filter = "(" + filter + ")";
+        }
+        return filter;
+    }
+
+
+    void doAction( String action, String urlParam ) throws IOException, ServletException
+    {
+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+        Repository[] repos = admin.listRepositories();
+        Repository repo = getRepository( repos, urlParam );
+
+        URL uri = repo != null ? repo.getURL() : new URL( urlParam );
+
+        if ( "delete".equals( action ) )
+        {
+            if ( !admin.removeRepository( uri ) )
+            {
+                throw new ServletException( "Failed to remove repository with URL " + uri );
+            }
+        }
+        else if ( "add".equals( action ) || "refresh".equals( action ) )
+        {
+            try
+            {
+                admin.addRepository( uri );
+            }
+            catch ( IOException e )
+            {
+                throw e;
+            }
+            catch ( Exception e )
+            {
+                throw new ServletException( "Failed to " + action + " repository " + uri + ": " + e.toString() );
+            }
+
+        }
+    }
+
+
+    final void doDeploy( String[] bundles, boolean start, boolean optional )
+    {
+        // check whether we have to do something
+        if ( bundles == null || bundles.length == 0 )
+        {
+            logger.log( "No resources to deploy" );
+            return;
+        }
+
+        RepositoryAdmin repoAdmin = ( RepositoryAdmin ) getRepositoryAdmin();
+        Resolver resolver = repoAdmin.resolver();
+
+        // prepare the deployment
+        for ( int i = 0; i < bundles.length; i++ )
+        {
+            String bundle = bundles[i];
+            if ( bundle == null || bundle.equals( "-" ) )
+            {
+                continue;
+            }
+
+            String filter = "(id=" + bundle + ")";
+            Resource[] resources = repoAdmin.discoverResources( filter );
+            if ( resources != null && resources.length > 0 )
+            {
+                resolver.add( resources[0] );
+            }
+        }
+
+        OsgiDeployer.deploy( resolver, logger, start );
+    }
+
+
+    private final Repository getRepository( Repository[] repos, String repositoryUrl )
+    {
+        if ( repositoryUrl == null || repositoryUrl.length() == 0 )
+        {
+            return null;
+        }
+
+        for ( int i = 0; i < repos.length; i++ )
+        {
+            if ( repositoryUrl.equals( repos[i].getURL().toString() ) )
+            {
+                return repos[i];
+            }
+        }
+
+        return null;
+    }
+
+
+    private final JSONObject toJSON( Resource resource, Bundle[] bundles, boolean details ) throws JSONException
+    {
+        final String symbolicName = resource.getSymbolicName();
+        final String version = resource.getVersion().toString();
+        boolean installed = false;
+        for ( int i = 0; symbolicName != null && !installed && bundles != null && i < bundles.length; i++ )
+        {
+            final String ver = ( String ) bundles[i].getHeaders( "" ).get( Constants.BUNDLE_VERSION );
+            installed = symbolicName.equals( bundles[i].getSymbolicName() ) && version.equals( ver );
+        }
+        JSONObject json = new JSONObject( resource.getProperties() ) //
+            .put( "id", resource.getId() ) //
+            .put( "presentationname", resource.getPresentationName() ) //
+            .put( "symbolicname", symbolicName ) //
+            .put( "url", resource.getURL() ) //
+            .put( "version", version ) //
+            .put( "categories", resource.getCategories() ) //
+            .put( "installed", installed );
+
+        if ( details )
+        {
+            Capability[] caps = resource.getCapabilities();
+            for ( int i = 0; caps != null && i < caps.length; i++ )
+            {
+                json.append( "capabilities", new JSONObject().put( "name", caps[i].getName() ).put( "properties",
+                    new JSONObject( caps[i].getProperties() ) ) );
+            }
+            Requirement[] reqs = resource.getRequirements();
+            for ( int i = 0; reqs != null && i < reqs.length; i++ )
+            {
+                json.append( "requirements", new JSONObject().put( "name", reqs[i].getName() ).put( "filter",
+                    reqs[i].getFilter() ).put( "optional", reqs[i].isOptional() ) );
+            }
+
+            final RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();
+            Resolver resolver = admin.resolver();
+            resolver.add( resource );
+            resolver.resolve(); // (Resolver.NO_OPTIONAL_RESOURCES);
+            Resource[] required = resolver.getRequiredResources();
+            for ( int i = 0; required != null && i < required.length; i++ )
+            {
+                json.append( "required", toJSON( required[i], bundles, false ) );
+            }
+            Resource[] optional = resolver.getOptionalResources();
+            for ( int i = 0; optional != null && i < optional.length; i++ )
+            {
+                json.append( "optional", toJSON( optional[i], bundles, false ) );
+            }
+            Requirement/*Reason*/[] unsatisfied = resolver.getUnsatisfiedRequirements();
+            for ( int i = 0; unsatisfied != null && i < unsatisfied.length; i++ )
+            {
+                json.append( "unsatisfied", new JSONObject().put( "name", unsatisfied[i].getName() ).put( "filter",
+                    unsatisfied[i].getFilter() ).put( "optional", unsatisfied[i].isOptional() ) );
+            }
+        }
+        return json;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiDeployer.java
similarity index 69%
copy from webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java
copy to webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiDeployer.java
index d814bb8..5533d30 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/DeployerThread.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/OsgiDeployer.java
@@ -19,14 +19,14 @@
 package org.apache.felix.webconsole.internal.obr;
 
 
-import org.apache.felix.bundlerepository.Reason;
-import org.apache.felix.bundlerepository.Resolver;
-import org.apache.felix.bundlerepository.Resource;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.osgi.service.log.LogService;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resolver;
+import org.osgi.service.obr.Resource;
 
 
-public class DeployerThread extends Thread
+public class OsgiDeployer implements Runnable
 {
 
     private final Resolver obrResolver;
@@ -35,42 +35,35 @@
 
     private final boolean startBundles;
 
-    private final boolean optionalDependencies;
 
-
-    public DeployerThread( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
-        boolean optionalDependencies )
+    static void deploy( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles )
     {
-        this( obrResolver, logger, startBundles, optionalDependencies, "OBR Bundle Deployer" );
+        final OsgiDeployer d = new OsgiDeployer( obrResolver, logger, startBundles );
+        final Thread t = new Thread( d, "OBR Bundle Deployer (OSGi API)" );
+        t.start();
     }
 
 
-    public DeployerThread( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,
-        boolean optionalDependencies, String name )
+    public OsgiDeployer( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles )
     {
-        super( name );
         this.obrResolver = obrResolver;
         this.logger = logger;
         this.startBundles = startBundles;
-        this.optionalDependencies = optionalDependencies;
     }
 
 
     public void run()
     {
-        int flags = 0;
-        flags += (startBundles ? Resolver.START : 0);
-        flags += (optionalDependencies ? 0 : Resolver.NO_OPTIONAL_RESOURCES);
         try
         {
-            if ( obrResolver.resolve( flags ) )
+            if ( obrResolver.resolve() )
             {
 
                 logResource( logger, "Installing Requested Resources", obrResolver.getAddedResources() );
                 logResource( logger, "Installing Required Resources", obrResolver.getRequiredResources() );
                 logResource( logger, "Installing Optional Resources", obrResolver.getOptionalResources() );
 
-                obrResolver.deploy( flags );
+                obrResolver.deploy( startBundles );
             }
             else
             {
@@ -99,17 +92,17 @@
     }
 
 
-    public static void logRequirements( AbstractWebConsolePlugin logger, String message, Reason[] reasons )
+    public static void logRequirements( AbstractWebConsolePlugin logger, String message, Requirement[] reasons )
     {
         logger.log( LogService.LOG_ERROR, message );
         for ( int i = 0; reasons != null && i < reasons.length; i++ )
         {
-            String moreInfo = reasons[i].getRequirement().getComment();
+            String moreInfo = reasons[i].getComment();
             if ( moreInfo == null )
             {
-                moreInfo = reasons[i].getRequirement().getFilter().toString();
+                moreInfo = reasons[i].getFilter().toString();
             }
-            logger.log( LogService.LOG_ERROR, "  " + i + ": " + reasons[i].getRequirement().getName() + " (" + moreInfo + ")" );
+            logger.log( LogService.LOG_ERROR, "  " + i + ": " + reasons[i].getName() + " (" + moreInfo + ")" );
         }
     }