You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ge...@apache.org on 2009/07/03 16:05:01 UTC

svn commit: r790920 - in /felix/trunk/karaf: ./ demos/smx4web/ deployer/blueprint/ deployer/features/ deployer/filemonitor/ deployer/spring/ gshell/gshell-config/ gshell/gshell-features/ gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshel...

Author: gertv
Date: Fri Jul  3 14:05:00 2009
New Revision: 790920

URL: http://svn.apache.org/viewvc?rev=790920&view=rev
Log:
FELIX-1261: Install/uninstall features from web console

Added:
    felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java   (with props)
    felix/trunk/karaf/webconsole/src/main/resources/res/
    felix/trunk/karaf/webconsole/src/main/resources/res/ui/
    felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js   (with props)
Modified:
    felix/trunk/karaf/demos/smx4web/   (props changed)
    felix/trunk/karaf/deployer/blueprint/   (props changed)
    felix/trunk/karaf/deployer/features/   (props changed)
    felix/trunk/karaf/deployer/filemonitor/   (props changed)
    felix/trunk/karaf/deployer/spring/   (props changed)
    felix/trunk/karaf/gshell/gshell-config/   (props changed)
    felix/trunk/karaf/gshell/gshell-features/   (props changed)
    felix/trunk/karaf/gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImpl.java
    felix/trunk/karaf/gshell/gshell-features/src/test/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImplTest.java
    felix/trunk/karaf/gshell/gshell-log/   (props changed)
    felix/trunk/karaf/gshell/gshell-obr/   (props changed)
    felix/trunk/karaf/gshell/gshell-osgi/   (props changed)
    felix/trunk/karaf/gshell/gshell-packages/   (props changed)
    felix/trunk/karaf/gshell/gshell-run/   (props changed)
    felix/trunk/karaf/itests/   (props changed)
    felix/trunk/karaf/jaas/jaas-boot/   (props changed)
    felix/trunk/karaf/jaas/jaas-config/   (props changed)
    felix/trunk/karaf/jaas/jaas-modules/   (props changed)
    felix/trunk/karaf/management/   (props changed)
    felix/trunk/karaf/pom.xml
    felix/trunk/karaf/tooling/features-maven-plugin/   (props changed)
    felix/trunk/karaf/webconsole/   (props changed)
    felix/trunk/karaf/webconsole/pom.xml
    felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java
    felix/trunk/karaf/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole.xml

Propchange: felix/trunk/karaf/demos/smx4web/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/deployer/blueprint/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
+maven-eclipse.xml
 eclipse-classes

Propchange: felix/trunk/karaf/deployer/features/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/deployer/filemonitor/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/deployer/spring/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
+maven-eclipse.xml
 eclipse-classes

Propchange: felix/trunk/karaf/gshell/gshell-config/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/gshell/gshell-features/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.iws
 *.ipr
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Modified: felix/trunk/karaf/gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImpl.java?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImpl.java (original)
+++ felix/trunk/karaf/gshell/gshell-features/src/main/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImpl.java Fri Jul  3 14:05:00 2009
@@ -328,6 +328,9 @@
     }
 
     protected Feature getFeature(String name, String version) throws Exception {
+        if (version != null) {
+            version = version.trim();
+        }
         Map<String, Feature> versions = getFeatures().get(name);
         if (versions == null || versions.isEmpty()) {
             return null;

Modified: felix/trunk/karaf/gshell/gshell-features/src/test/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImplTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/gshell/gshell-features/src/test/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImplTest.java?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/gshell/gshell-features/src/test/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImplTest.java (original)
+++ felix/trunk/karaf/gshell/gshell-features/src/test/java/org/apache/felix/karaf/gshell/features/internal/FeaturesServiceImplTest.java Fri Jul  3 14:05:00 2009
@@ -42,6 +42,21 @@
         assertSame(feature, impl.getFeature("transaction", FeatureImpl.DEFAULT_VERSION));
     }
     
+    public void testGetFeatureStripVersion() throws Exception {
+        final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
+        Map<String, Feature> versions = new HashMap<String, Feature>();
+        FeatureImpl feature = new FeatureImpl("transaction");
+        versions.put("1.0.0", feature);
+        features.put("transaction", versions);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl() {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            };
+        };
+        assertNotNull(impl.getFeature("transaction", "  1.0.0  "));
+        assertSame(feature, impl.getFeature("transaction", "  1.0.0   "));
+    }
+    
     public void testGetFeatureNotAvailable() throws Exception {
         final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
         Map<String, Feature> versions = new HashMap<String, Feature>();

Propchange: felix/trunk/karaf/gshell/gshell-log/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/gshell/gshell-obr/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.iws
 *.ipr
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/gshell/gshell-osgi/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/gshell/gshell-packages/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/gshell/gshell-run/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.iws
 *.ipr
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/itests/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.iws
 *.ipr
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/jaas/jaas-boot/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/jaas/jaas-config/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/jaas/jaas-modules/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -3,6 +3,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Propchange: felix/trunk/karaf/management/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -5,6 +5,8 @@
 *.ipr
 *.iws
 .classpath
+.externalToolBuilders
 .project
 .settings
 eclipse-classes
+maven-eclipse.xml

Modified: felix/trunk/karaf/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/pom.xml?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/pom.xml (original)
+++ felix/trunk/karaf/pom.xml Fri Jul  3 14:05:00 2009
@@ -88,7 +88,7 @@
         <felix.osgi.version>1.3.0-SNAPSHOT</felix.osgi.version>
         <felix.plugin.version>2.0.0</felix.plugin.version>
         <felix.prefs.version>1.0.2</felix.prefs.version>
-        <felix.webconsole.version>1.2.10</felix.webconsole.version>
+        <felix.webconsole.version>1.2.11-SNAPSHOT</felix.webconsole.version>
         <felix.metatype.version>1.0.2</felix.metatype.version>
         <geronimo.annotation.version>1.1.1</geronimo.annotation.version>
         <geronimo.blueprint.version>1.0.0-SNAPSHOT</geronimo.blueprint.version>

Propchange: felix/trunk/karaf/tooling/features-maven-plugin/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -4,6 +4,7 @@
 .settings
 .externalToolBuilders
 eclipse-classes
+maven-eclipse.xml
 *.iml
 *.ipr
 *.iws

Propchange: felix/trunk/karaf/webconsole/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Fri Jul  3 14:05:00 2009
@@ -1,4 +1,7 @@
+eclipse-classes
+maven-eclipse.xml
 target
 .classpath
+.externalToolBuilders
 .project
 .settings

Modified: felix/trunk/karaf/webconsole/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/webconsole/pom.xml?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/webconsole/pom.xml (original)
+++ felix/trunk/karaf/webconsole/pom.xml Fri Jul  3 14:05:00 2009
@@ -32,7 +32,7 @@
   <artifactId>org.apache.felix.karaf.webconsole</artifactId>
   <packaging>bundle</packaging>
   <version>1.2.0-SNAPSHOT</version>
-  <name>Apache Felix Karaf :: Web Console</name>
+  <name>Apache Felix Karaf :: Web Console Features Plugin</name>
   
   <dependencies>
     <dependency>
@@ -56,6 +56,11 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
       <groupId>org.apache.felix.karaf.gshell</groupId>
       <artifactId>org.apache.felix.karaf.gshell.features</artifactId>
     </dependency>
@@ -64,6 +69,13 @@
       <artifactId>org.apache.servicemix.bundles.junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.json</groupId>
+      <artifactId>json</artifactId>
+      <version>20070829</version>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
   
   <build>
@@ -73,7 +85,12 @@
         <artifactId>maven-bundle-plugin</artifactId>
         <configuration>
           <instructions>
-            <Private-Package>org.apache.felix.karaf.webconsole*</Private-Package>
+            <Export-Package>org.apache.felix.karaf.webconsole;version=${pom.version}</Export-Package>
+            <Embed-Dependency>
+               <!-- Required for JSON data transfer -->
+               <!-- TODO: this needs to be put in a common place for reuse. -->
+               json
+            </Embed-Dependency>
           </instructions>
         </configuration>
       </plugin>

Added: felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java?rev=790920&view=auto
==============================================================================
--- felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java (added)
+++ felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java Fri Jul  3 14:05:00 2009
@@ -0,0 +1,47 @@
+/*
+ * 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.karaf.webconsole;
+
+/**
+ * Represents a feature with a name, version and state 
+ */
+public class Feature {
+
+  public enum State {
+    INSTALLED, UNINSTALLED, UNKNOWN;
+
+    @Override
+    public String toString() {
+      //only capitalize the first letter
+      String s = super.toString();
+      return s.substring( 0, 1 ) + s.substring( 1 ).toLowerCase();
+    }
+  };
+
+  protected String name;
+
+  protected String version;
+
+  protected State state;
+
+
+  public Feature(String name, String version, State state) {
+    this.name = name;
+    this.version = version;
+    this.state = state;
+  }
+}

Propchange: felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/Feature.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java (original)
+++ felix/trunk/karaf/webconsole/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java Fri Jul  3 14:05:00 2009
@@ -16,83 +16,538 @@
  */
 package org.apache.felix.karaf.webconsole;
 
+
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
-import javax.servlet.Servlet;
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Comparator;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.felix.karaf.gshell.features.FeaturesService;
+import org.apache.felix.karaf.gshell.features.Repository;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 
+import org.json.JSONException;
+import org.json.JSONWriter;
+
+import org.osgi.framework.BundleContext;
+
+
 /**
- * Felix Web Console plugin to interact with Karaf features
- *
- * @author Marcin Wilkos
+ * The <code>FeaturesPlugin</code>
  */
-public class FeaturesPlugin extends AbstractWebConsolePlugin implements Servlet {
+public class FeaturesPlugin extends AbstractWebConsolePlugin
+{
+
+    /** Pseudo class version ID to keep the IDE quite. */
+    private static final long serialVersionUID = 1L;
+
+    public static final String NAME = "features";
+
+    public static final String LABEL = "Features";
+
+    private Log log = LogFactory.getLog(FeaturesPlugin.class);
+
+    private ClassLoader classLoader;
+
+    private String featuresJs = "/features/res/ui/features.js";
 
-    private static final String LABEL = "features";
-    private static final String TITLE = "Features";
-    
     private FeaturesService featuresService;
     
-    public FeaturesPlugin() {
-        super();
+    private BundleContext bundleContext;
+
+
+    /*
+     * Blueprint lifecycle callback methods
+     */
+    
+    public void start()
+    {
+        super.activate( bundleContext );
+
+        this.classLoader = this.getClass().getClassLoader();
+
+        this.log.info( LABEL + " plugin activated" );
+    }
+
+    public void stop()
+    {
+        this.log.info( LABEL + " plugin deactivated" );
+        super.deactivate();
     }
 
-    public String getTitle() {
-        return TITLE;
+    //
+    // AbstractWebConsolePlugin interface
+    //    
+    public String getLabel()
+    {
+        return NAME;
     }
 
-    public String getLabel() {
+
+    public String getTitle()
+    {
         return LABEL;
     }
 
-    protected void renderContent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-        PrintWriter pw = response.getWriter();
-        pw.println("<pre>");
-        pw.println("</pre>");
-
-        pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<th class='content container'>" + getTitle() + "</th>" );
-        pw.println( "</tr>" );
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td class='content'>" );
-        pw.println( "<pre>" );
-
-        pw.println("*** Features:");
-        String[] features;
-        try {
-            features = getFeatures();
-        } catch (Exception e) {
-            throw new ServletException("Unable to fetch features", e);
-        }
-        for(int i=0; i<features.length;i++){
-            pw.println(features[i]);
-        }
-
-        pw.println( "</pre>" );
-        pw.println( "</td>" );
-        pw.println( "</tr>" );
-        pw.println( "</table>" );
+
+    protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
+    {
+        boolean success = false;
+
+        final String action = req.getParameter( "action" );
+        final String feature = req.getParameter( "feature" );
+        final String version = req.getParameter( "version" );
+        final String url = req.getParameter( "url" );
+
+        if ( action == null )
+        {
+            success = true;
+        }
+        else if ( "installFeature".equals( action ) )
+        {
+            success = this.installFeature(feature, version);
+        }
+        else if ( "uninstallFeature".equals( action ) )
+        {
+            success = this.uninstallFeature( feature, version );
+        }
+        else if ( "refreshRepository".equals( action ) )
+        {
+            success = this.refreshRepository( url );
+        }
+        else if ( "removeRepository".equals( action ) )
+        {
+            success = this.removeRepository( url );
+        }
+        else if ( "addRepository".equals( action ) )
+        {
+            success = this.addRepository( url );
+        }
+
+        if ( success )
+        {
+            // let's wait a little bit to give the framework time
+            // to process our request
+            try
+            {
+                Thread.sleep( 800 );
+            }
+            catch ( InterruptedException e )
+            {
+                // we ignore this
+            }
+            this.renderJSON( resp, null );
+        }
+        else
+        {
+            super.doPost( req, resp );
+        }
     }
 
-    /*
-     * Get the list of installed/uninstalled features
-     */
-    private String[] getFeatures() throws Exception {        
-        return getFeaturesService().listFeatures();
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
+
+        // get request info from request attribute
+        final PrintWriter pw = response.getWriter();
+
+        String appRoot = ( String ) request
+            .getAttribute( "org.apache.felix.webconsole.internal.servlet.OsgiManager.appRoot" );
+        final String featuresScriptTag = "<script src='" + appRoot + this.featuresJs
+            + "' language='JavaScript'></script>";
+        pw.println( featuresScriptTag );
+
+        pw.println( "<script type='text/javascript'>" );
+        pw.println( "// <![CDATA[" );
+        pw.println( "var imgRoot = '" + appRoot + "/res/imgs';" );
+        pw.println( "// ]]>" );
+        pw.println( "</script>" );
+
+        pw.println( "<div id='plugin_content'/>" );
+
+        pw.println( "<script type='text/javascript'>" );
+        pw.println( "// <![CDATA[" );
+        pw.print( "renderFeatures( " );
+        writeJSON( pw );
+        pw.println( " )" );
+        pw.println( "// ]]>" );
+        pw.println( "</script>" );
     }
-    
-    public FeaturesService getFeaturesService() {
-        return featuresService;
+
+
+    //
+    // Additional methods
+    //
+
+    protected URL getResource( String path )
+    {
+        path = path.substring( NAME.length() + 1 );
+        URL url = this.classLoader.getResource( path );
+        try
+        {
+            InputStream ins = url.openStream();
+            if ( ins == null )
+            {
+                this.log.error( "failed to open " + url );
+            }
+        }
+        catch ( IOException e )
+        {
+            this.log.error( e.getMessage(), e );
+        }
+        return url;
+    }
+
+
+    private boolean installFeature(String feature, String version) {
+        boolean success = false;
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+        }
+        try
+        {
+            featuresService.installFeature( feature, version );
+            success = true;
+        }
+        catch ( Exception e )
+        {
+            this.log.error( "failed to install feature: ", e );
+        }
+        return success;
+    }
+
+
+    private boolean uninstallFeature(String feature, String version) {
+        boolean success = false;
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+        }
+        try
+        {
+            featuresService.uninstallFeature( feature, version );
+            success = true;
+        }
+        catch ( Exception e )
+        {
+            this.log.error( "failed to install feature: ", e );
+        }
+        return success;
+    }
+
+
+    private boolean removeRepository(String url) {
+        boolean success = false;
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+        }
+        try
+        {
+            featuresService.removeRepository( new URI( url ) );
+            success = true;
+        }
+        catch ( Exception e )
+        {
+            this.log.error( "failed to install feature: ", e );
+        }
+        return success;
+    }
+
+
+    private boolean refreshRepository(String url) {
+        boolean success = false;
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+        }
+        try
+        {
+            featuresService.removeRepository( new URI( url ) );
+            featuresService.addRepository( new URI( url ) );
+            success = true;
+        }
+        catch ( Exception e )
+        {
+            this.log.error( "failed to install feature: ", e );
+        }
+        return success;
+    }
+
+
+    private boolean addRepository(String url) {
+        boolean success = false;
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+        }
+        try
+        {
+            featuresService.addRepository( new URI( url ) );
+            success = true;
+        }
+        catch ( Exception e )
+        {
+            this.log.error( "failed to install feature: ", e );
+        }
+        return success;
+    }
+
+
+    private void renderJSON( final HttpServletResponse response, final String feature ) throws IOException
+    {
+        response.setContentType( "application/json" );
+        response.setCharacterEncoding( "UTF-8" );
+
+        final PrintWriter pw = response.getWriter();
+        writeJSON( pw );
+    }
+
+
+    private void writeJSON( final PrintWriter pw ) throws IOException
+    {
+        final Feature[] features = this.getFeatures();
+        final String statusLine = this.getStatusLine( features );
+        final String[] repositories = this.getRepositories();
+
+        final JSONWriter jw = new JSONWriter( pw );
+
+        try
+        {
+            jw.object();
+
+            jw.key( "status" );
+            jw.value( statusLine );
+
+            jw.key( "features" );
+            jw.array();
+            for ( int i = 0; i < features.length; i++ )
+            {
+                featureInfo( jw, features[i] );
+            }
+            jw.endArray();
+
+            jw.key( "repositories" );
+            jw.array();
+            for ( int i = 0; i < repositories.length; i++ )
+            {
+                jw.object();
+                jw.key( "url" );
+                jw.value( repositories[i] );
+                jw.key( "actions" );
+                jw.array();
+                action( jw, true, "refreshRepository", "Refresh", "refresh" );
+                action( jw, true, "removeRepository", "Uninstall", "delete" );
+                jw.endArray();
+                jw.endObject();
+            }
+            jw.endArray();
+
+            jw.endObject();
+
+        }
+        catch ( JSONException je )
+        {
+            throw new IOException( je.toString() );
+        }
+
+    }
+
+
+    private String[] getRepositories()
+    {
+        String[] repositories = new String[0];
+
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+            return repositories;
+        }
+
+        Repository[] repositoryInfo = null;
+        try
+        {
+            repositoryInfo = featuresService.listRepositories();
+        }
+        catch ( Exception e )
+        {
+            this.log.error( e.getMessage() );
+            return new String[0];
+        }
+
+        repositories = new String[repositoryInfo.length];
+        for ( int i = 0; i < repositoryInfo.length; i++ )
+        {
+            repositories[i] = repositoryInfo[i].getURI().toString();
+        }
+        return repositories;
+    }
+
+
+    private Feature[] getFeatures()
+    {
+        Feature[] features = new Feature[0];
+
+        if ( featuresService == null )
+        {
+            this.log.error( "GShell Features service is unavailable." );
+            return features;
+        }
+
+        String[] featureInfo = null;
+        try
+        {
+            featureInfo = featuresService.listFeatures();
+        }
+        catch ( Exception e )
+        {
+            this.log.error( e.getMessage() );
+            return new Feature[0];
+        }
+
+        features = new Feature[featureInfo.length];
+        for ( int i = 0; i < featureInfo.length; i++ )
+        {
+            String[] temp;
+            temp = getBracketedToken( featureInfo[i], 0 );
+            Feature.State state;
+            if ( "installed  ".equals( temp[0] ) )
+            {
+                state = Feature.State.INSTALLED;
+            }
+            else if ( "uninstalled".equals( temp[0] ) )
+            {
+                state = Feature.State.UNINSTALLED;
+            }
+            else
+            {
+                state = Feature.State.UNKNOWN;
+            }
+            temp = getBracketedToken( temp[1], 0 );
+            String version = temp[0];
+            features[i] = new Feature( temp[1].trim(), version, state );
+        }
+        Arrays.sort( features, new FeatureComparator() );
+        return features;
+    }
+
+    private String[] getBracketedToken( String str, int startIndex )
+    {
+        int start = str.indexOf( '[', startIndex ) + 1;
+        int end = str.indexOf( ']', start );
+        String token = str.substring( start, end );
+        String remainder = str.substring( end + 1 );
+        return new String[]
+            { token, remainder };
+    }
+
+
+    class FeatureComparator implements Comparator<Feature>
+    {
+        public int compare( Feature o1, Feature o2 )
+        {
+            return o1.name.toLowerCase().compareTo( o2.name.toLowerCase() );
+        }
+    }
+
+
+    private String getStatusLine( final Feature[] features )
+    {
+        int installed = 0;
+        for ( int i = 0; i < features.length; i++ )
+        {
+            if ( features[i].state == Feature.State.INSTALLED )
+            {
+                installed++;
+            }
+        }
+        final StringBuffer buffer = new StringBuffer();
+        buffer.append( "Feature information: " );
+        appendFeatureInfoCount( buffer, "in total", features.length );
+        if ( installed == features.length )
+        {
+            buffer.append( " - all " );
+            appendFeatureInfoCount( buffer, "active.", features.length );
+        }
+        else
+        {
+            if ( installed != 0 )
+            {
+                buffer.append( ", " );
+                appendFeatureInfoCount( buffer, "installed", installed );
+            }
+            buffer.append( '.' );
+        }
+        return buffer.toString();
+    }
+
+
+    private void appendFeatureInfoCount( final StringBuffer buf, String msg, int count )
+    {
+        buf.append( count );
+        buf.append( " feature" );
+        if ( count != 1 )
+            buf.append( 's' );
+        buf.append( ' ' );
+        buf.append( msg );
+    }
+
+
+    private void featureInfo( JSONWriter jw, Feature feature ) throws JSONException
+    {
+        jw.object();
+        jw.key( "name" );
+        jw.value( feature.name );
+        jw.key( "version" );
+        jw.value( feature.version );
+        jw.key( "state" );
+        jw.value( feature.state );
+
+        jw.key( "actions" );
+        jw.array();
+
+        if ( feature.state == Feature.State.INSTALLED )
+        {
+            action( jw, true, "uninstallFeature", "Uninstall", "delete" );
+        }
+        else
+        {
+            action( jw, true, "installFeature", "Install", "start" );
+        }
+        jw.endArray();
+
+        jw.endObject();
+    }
+
+
+    private void action( JSONWriter jw, boolean enabled, String op, String title, String image ) throws JSONException
+    {
+        jw.object();
+        jw.key( "enabled" ).value( enabled );
+        jw.key( "op" ).value( op );
+        jw.key( "title" ).value( title );
+        jw.key( "image" ).value( image );
+        jw.endObject();
     }
     
-    public void setFeaturesService(FeaturesService featuresService) {
+    // DI setters
+    public void setFeaturesService(FeaturesService featuresService) 
+    {
         this.featuresService = featuresService;
     }
+    
+    public void setBundleContext(BundleContext bundleContext) 
+    {
+        this.bundleContext = bundleContext;
+    }
 }

Modified: felix/trunk/karaf/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole.xml
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole.xml?rev=790920&r1=790919&r2=790920&view=diff
==============================================================================
--- felix/trunk/karaf/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole.xml (original)
+++ felix/trunk/karaf/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole.xml Fri Jul  3 14:05:00 2009
@@ -22,8 +22,9 @@
 
     <reference id="featuresService" interface="org.apache.felix.karaf.gshell.features.FeaturesService" />
 
-    <bean id="featuresPlugin" class="org.apache.felix.karaf.webconsole.FeaturesPlugin">
+    <bean id="featuresPlugin" class="org.apache.felix.karaf.webconsole.FeaturesPlugin" init-method="start" destroy-method="stop">
         <property name="featuresService" ref="featuresService" />
+        <property name="bundleContext" ref="blueprintBundleContext" />
     </bean>
 
     <service ref="featuresPlugin" interface="javax.servlet.Servlet" >

Added: felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js?rev=790920&view=auto
==============================================================================
--- felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js (added)
+++ felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js Fri Jul  3 14:05:00 2009
@@ -0,0 +1,184 @@
+/*
+ * 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 renderFeatures( data ) {
+    $(document).ready( function() {
+        renderView();
+        renderData( data );
+        $("#repository_table").tablesorter( {
+            headers: {
+                1: { sorter: false }
+            },
+            sortList: [[0,0]],
+        } );
+        $("#feature_table").tablesorter( {
+            headers: {
+                3: { sorter: false }
+            },
+            sortList: [[0,0]],
+        } );
+    } );
+}
+
+function renderView() {
+    renderStatusLine();
+    renderTable( "Feature Repositories", "repository_table", ["URL", "Actions"] );
+    var txt = "<form method='post'><div class='table'><table class='tablelayout'><tbody><tr>" +
+        "<input type='hidden' name='action' value='addRepository'/>" +
+        "<td><input id='url' type='text' name='url' style='width:100%'/></td>" +
+        "<td class='col_Actions'><input type='button' value='Add URL' onclick='addRepositoryUrl()'/></td>" +
+        "</tr></tbody></table></div></form><br/>";
+    $("#plugin_content").append( txt );
+    renderTable( "Features", "feature_table", ["Name", "Version", "Status", "Actions"] );
+    renderStatusLine();
+}
+
+function addRepositoryUrl() {
+    var url = document.getElementById( "url" ).value;
+    changeRepositoryState( "addRepository", url );
+}
+
+function renderStatusLine() {
+    $("#plugin_content").append( "<div class='fullwidth'><div class='statusline'/></div>" );
+}
+
+function renderTable( /* String */ title, /* String */ id, /* array of Strings */ columns ) {
+    var txt = "<div class='table'><table class='tablelayout'><tbody><tr>" +
+        "<td style='color:#6181A9;background-color:#e6eeee'>" +
+        title + "</td></tr></tbody></table>" +
+        "<table id='" + id + "' class='tablelayout'><thead><tr>";
+    for ( var name in columns ) {
+      txt = txt + "<th class='col_" + columns[name] + "' style='border-top:#e6eeee'>" + columns[name] + "</th>";
+    }
+    txt = txt + "</tr></thead><tbody></tbody></table></div>";
+    $("#plugin_content").append( txt );
+}
+
+function renderData( /* Object */ data ) {
+    renderStatusData( data.status );
+    renderRepositoryTableData( data.repositories );
+    renderFeatureTableData( data.features );
+}
+
+function renderStatusData( /* String */ status )  {
+    $(".statusline").empty().append( status );
+}
+
+function renderRepositoryTableData( /* array of Objects */ repositories ) {
+    var trElement;
+    var input;
+    $("#repository_table > tbody > tr").remove();
+    for ( var idx in repositories ) {
+        trElement = tr( null, { id: "repository-" + idx } );
+        renderRepositoryData( trElement, repositories[idx] );
+        $("#repository_table > tbody").append( trElement ); 
+    }
+    $("#repository_table").trigger( "update" );
+}
+
+function renderRepositoryData( /* Element */ parent, /* Object */ repository ) {
+    parent.appendChild( td( null, null, [text( repository.url )] ) );
+
+    var actionsTd = td( null, null );
+    var div = createElement( "div", null, {
+      style: { "text-align": "left"}
+    } );
+    actionsTd.appendChild( div );
+    
+    for ( var a in repository.actions ) {
+      repositoryButton( div, repository.url, repository.actions[a] );
+    }
+    parent.appendChild( actionsTd );
+}
+
+function repositoryButton( /* Element */ parent, /* String */ url, /* Obj */ action ) {
+    if ( !action.enabled ) {
+        return;
+    }
+  
+    var input = createElement( "input", null, {
+        type: 'image',
+        style: {"margin-left": "10px"},
+        title: action.title,
+        alt: action.title,
+        src: imgRoot + '/bundle_' + action.image + '.png'
+    } );
+    $(input).click( function() {changeRepositoryState( action.op, url )} );
+
+    if ( !action.enabled ) {
+        $(input).attr( "disabled", true );
+    }
+    parent.appendChild( input );
+}
+
+function changeRepositoryState( /* String */ action, /* String */ url ) {
+    $.post( pluginRoot, {"action": action, "url": url}, function( data ) {
+        renderData( data );
+    }, "json" ); 
+}
+
+function renderFeatureTableData( /* array of Objects */ features ) {
+    $("#feature_table > tbody > tr").remove();
+    for ( var idx in features ) {
+        var trElement = tr( null, { id: "feature-" + idx } );
+        renderFeatureData( trElement, features[idx] );
+        $("#feature_table > tbody").append( trElement ); 
+    }
+    $("#feature_table").trigger( "update" );
+}
+
+function renderFeatureData( /* Element */ parent, /* Object */ feature ) {
+    parent.appendChild( td( null, null, [ text( feature.name ) ] ) );
+    parent.appendChild( td( null, null, [ text( feature.version ) ] ) );
+    parent.appendChild( td( null, null, [ text( feature.state ) ] ) );
+    var actionsTd = td( null, null );
+    var div = createElement( "div", null, {
+        style: { "text-align": "left"}
+    } );
+    actionsTd.appendChild( div );
+    
+    for ( var a in feature.actions ) {
+        featureButton( div, feature.name, feature.version, feature.actions[a] );
+    }
+    parent.appendChild( actionsTd );
+}
+
+function featureButton( /* Element */ parent, /* String */ name, /* String */ version, /* Obj */ action ) {
+    if ( !action.enabled ) {
+        return;
+    }
+  
+    var input = createElement( "input", null, {
+        type: 'image',
+        style: {"margin-left": "10px"},
+        title: action.title,
+        alt: action.title,
+        src: imgRoot + '/bundle_' + action.image + '.png'
+    } );
+    $(input).click( function() {changeFeatureState( action.op, name, version )} );
+
+    if ( !action.enabled ) {
+        $(input).attr( "disabled", true );
+    }
+    parent.appendChild( input );
+}
+
+function changeFeatureState( /* String */ action, /* String */ feature, /* String */ version ) {
+    $.post( pluginRoot, {"action": action, "feature": feature, "version": version}, function( data ) {
+        renderData( data );
+    }, "json" ); 
+}

Propchange: felix/trunk/karaf/webconsole/src/main/resources/res/ui/features.js
------------------------------------------------------------------------------
    svn:eol-style = native