FELIX-1555: The osgi:list command should print spring-dm bundle state if spring-dm has been deployed

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@812501 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/gshell/gshell-osgi/pom.xml b/karaf/gshell/gshell-osgi/pom.xml
index a455c56..2ef8131 100644
--- a/karaf/gshell/gshell-osgi/pom.xml
+++ b/karaf/gshell/gshell-osgi/pom.xml
@@ -48,6 +48,17 @@
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-core</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-extender</artifactId>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 
     <build>
@@ -63,8 +74,12 @@
                             org.osgi.service.command,
                             org.apache.felix.gogo.commands,
                             org.apache.felix.karaf.gshell.console,
+                            org.springframework*;resolution:=optional,
                             *
                         </Import-Package>
+                        <DynamicImport-Package>
+                            org.springframework.*
+                        </DynamicImport-Package>
                         <Private-Package>!*</Private-Package>
                         <_versionpolicy>${bnd.version.policy}</_versionpolicy>
                     </instructions>
diff --git a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BlueprintListener.java b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BlueprintListener.java
index b80c3d3..7da1a74 100644
--- a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BlueprintListener.java
+++ b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BlueprintListener.java
@@ -32,7 +32,9 @@
  * TODO: use event admin to receive WAIT topics notifications from blueprint extender
  *
  */
-public class BlueprintListener implements org.osgi.service.blueprint.container.BlueprintListener, BundleListener {
+public class BlueprintListener implements org.osgi.service.blueprint.container.BlueprintListener, BundleListener,
+                                            BundleStateListener, BundleStateListener.Factory
+{
 
     public static enum BlueprintState {
         Unknown,
@@ -54,6 +56,22 @@
         this.states = new ConcurrentHashMap<Long, BlueprintState>();
     }
 
+    public String getName() {
+        return "Blueprint   ";
+    }
+
+    public String getState(Bundle bundle) {
+        BlueprintState state = states.get(bundle.getBundleId());
+        if (state == null || bundle.getState() != Bundle.ACTIVE || state == BlueprintState.Unknown) {
+            return null;
+        }
+        return state.toString();
+    }
+
+    public BundleStateListener getListener() {
+        return this;
+    }
+
     public BlueprintState getBlueprintState(Bundle bundle) {
         BlueprintState state = states.get(bundle.getBundleId());
         if (state == null || bundle.getState() != Bundle.ACTIVE) {
diff --git a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BundleStateListener.java b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BundleStateListener.java
new file mode 100644
index 0000000..3f5116c
--- /dev/null
+++ b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/BundleStateListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.gshell.osgi;
+
+import org.osgi.framework.Bundle;
+
+public interface BundleStateListener {
+
+    public interface Factory {
+
+        BundleStateListener getListener();
+
+    }
+
+    String getName();
+
+    String getState(Bundle bundle);
+
+}
diff --git a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/ListBundles.java b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/ListBundles.java
index 426403c..564c234 100644
--- a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/ListBundles.java
+++ b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/ListBundles.java
@@ -16,6 +16,8 @@
  */
 package org.apache.felix.karaf.gshell.osgi;
 
+import java.util.List;
+
 import org.apache.felix.karaf.gshell.console.OsgiCommandSupport;
 import org.apache.felix.gogo.commands.Option;
 import org.apache.felix.gogo.commands.Command;
@@ -37,14 +39,10 @@
     @Option(name = "-u", description = "Show update")
     boolean showUpdate;
 
-    private BlueprintListener blueprintListener;
+    private List<BundleStateListener.Factory> bundleStateListenerFactories;
 
-    public BlueprintListener getBlueprintListener() {
-        return blueprintListener;
-    }
-
-    public void setBlueprintListener(BlueprintListener blueprintListener) {
-        this.blueprintListener = blueprintListener;
+    public void setBundleStateListenerFactories(List<BundleStateListener.Factory> bundleStateListenerFactories) {
+        this.bundleStateListenerFactories = bundleStateListenerFactories;
     }
 
     protected Object doExecute() throws Exception {
@@ -85,7 +83,15 @@
                msg = " Update location";
             }
             String level = (sl == null) ? "" : "  Level ";
-            System.out.println("   ID   State         Blueprint   " + level + msg);
+            String headers = "   ID   State       ";
+            for (BundleStateListener.Factory factory : bundleStateListenerFactories) {
+                BundleStateListener listener = factory.getListener();
+                if (listener != null) {
+                    headers += "  " + listener.getName() + " ";
+                }
+            }
+            headers += level + msg;
+            System.out.println(headers);
             for (int i = 0; i < bundles.length; i++) {
                 // Get the bundle name or location.
                 String name = (String) bundles[i].getHeaders().get(Constants.BUNDLE_NAME);
@@ -124,10 +130,16 @@
                 while (id.length() < 4) {
                     id = " " + id;
                 }
-                System.out.println("[" + id + "] ["
-                    + getStateString(bundles[i])
-                    + "] [" + getBlueprintStateString(bundles[i])
-                    + "] [" + level + "] " + name);
+                String line = "[" + id + "] [" + getStateString(bundles[i]) + "]";
+                for (BundleStateListener.Factory factory : bundleStateListenerFactories) {
+                    BundleStateListener listener = factory.getListener();
+                    if (listener != null) {
+                        String state = listener.getState(bundles[i]);
+                        line += " [" + getStateString(state, listener.getName().length()) + "]";
+                    }
+                }
+                line += " [" + level + "] " + name;
+                System.out.println(line);
 
                 if (admin != null) {
                     Bundle[] fragments = admin.getFragments(bundles[i]);
@@ -190,25 +202,13 @@
         }
     }
 
-    public String getBlueprintStateString(Bundle bundle) {
-        BlueprintListener.BlueprintState state = blueprintListener.getBlueprintState(bundle);
-        switch (state) {
-            case Creating:
-                return "Creating   ";
-            case Created:
-                return "Created    ";
-            case Destroying:
-                return "Destroying ";
-            case Destroyed:
-                return "Destroyed  ";
-            case Failure:
-                return "Failure    ";
-            case GracePeriod:
-                return "GracePeriod";
-            case Waiting:
-                return "Waiting    ";
-            default:
-                return "           ";
+    public String getStateString(String state, int length) {
+        if (state == null) {
+            state = "";
         }
+        while (state.length() < length) {
+            state += " ";
+        }
+        return state;
     }
 }
diff --git a/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/SpringStateListenerFactory.java b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/SpringStateListenerFactory.java
new file mode 100644
index 0000000..d394658
--- /dev/null
+++ b/karaf/gshell/gshell-osgi/src/main/java/org/apache/felix/karaf/gshell/osgi/SpringStateListenerFactory.java
@@ -0,0 +1,149 @@
+/*
+ * 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.gshell.osgi;
+
+import java.util.Map;
+import java.util.Hashtable;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.osgi.context.event.OsgiBundleApplicationContextListener;
+import org.springframework.osgi.context.event.OsgiBundleApplicationContextEvent;
+import org.springframework.osgi.context.event.OsgiBundleContextFailedEvent;
+import org.springframework.osgi.context.event.OsgiBundleContextRefreshedEvent;
+import org.springframework.osgi.extender.event.BootstrappingDependencyEvent;
+import org.springframework.osgi.service.importer.event.OsgiServiceDependencyEvent;
+import org.springframework.osgi.service.importer.event.OsgiServiceDependencyWaitStartingEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SpringStateListenerFactory implements BundleStateListener.Factory {
+
+    private BundleContext bundleContext;
+    private BundleStateListener listener;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void destroy() throws Exception {
+        if (listener instanceof Destroyable) {
+            ((Destroyable) listener).destroy();
+        }
+    }
+
+    public BundleStateListener getListener() {
+        if (listener == null) {
+            listener = createListener();
+        }
+        return listener;
+    }
+
+    private BundleStateListener createListener() {
+        try {
+            return new SpringApplicationListener(bundleContext);
+        } catch (Throwable t) {
+            return null;
+        }
+    }
+
+    public static interface Destroyable {
+
+        public void destroy() throws Exception;
+
+    }
+
+    public static class SpringApplicationListener implements OsgiBundleApplicationContextListener,
+            BundleListener, Destroyable, BundleStateListener {
+
+        public static enum SpringState {
+            Unknown,
+            Waiting,
+            Started,
+            Failed,
+        }
+
+        private static final Logger LOG = LoggerFactory.getLogger(BlueprintListener.class);
+
+        private final Map<Long, SpringState> states;
+        private BundleContext bundleContext;
+        private ServiceRegistration registration;
+
+        public SpringApplicationListener(BundleContext bundleContext) {
+            this.states = new ConcurrentHashMap<Long, SpringState>();
+            this.bundleContext = bundleContext;
+            this.bundleContext.addBundleListener(this);
+            this.registration = this.bundleContext.registerService(OsgiBundleApplicationContextListener.class.getName(), this, new Hashtable());
+        }
+
+        public void destroy() throws Exception {
+            bundleContext.removeBundleListener(this);
+            registration.unregister();
+        }
+
+        public String getName() {
+            return "Spring ";
+        }
+
+        public String getState(Bundle bundle) {
+            SpringState state = states.get(bundle.getBundleId());
+            if (state == null || bundle.getState() != Bundle.ACTIVE || state == SpringState.Unknown) {
+                return null;
+            }
+            return state.toString();
+        }
+
+        public SpringState getSpringState(Bundle bundle) {
+            SpringState state = states.get(bundle.getBundleId());
+            if (state == null || bundle.getState() != Bundle.ACTIVE) {
+                state = SpringState.Unknown;
+            }
+            return state;
+        }
+
+        public void onOsgiApplicationEvent(OsgiBundleApplicationContextEvent event) {
+            SpringState state = null;
+            if (event instanceof BootstrappingDependencyEvent) {
+                OsgiServiceDependencyEvent de = ((BootstrappingDependencyEvent) event).getDependencyEvent();
+                if (de instanceof OsgiServiceDependencyWaitStartingEvent) {
+                    state = SpringState.Waiting;
+                }
+            } else if (event instanceof OsgiBundleContextFailedEvent) {
+                state = SpringState.Failed;
+            } else if (event instanceof OsgiBundleContextRefreshedEvent) {
+                state = SpringState.Started;
+            }
+            if (state != null) {
+                LOG.debug("Spring app state changed to " + state + " for bundle " + event.getBundle().getBundleId());
+                states.put(event.getBundle().getBundleId(), state);
+            }
+        }
+
+        public void bundleChanged(BundleEvent event) {
+            if (event.getType() == BundleEvent.UNINSTALLED) {
+                states.remove(event.getBundle().getBundleId());
+            }
+        }
+
+    }
+
+}
diff --git a/karaf/gshell/gshell-osgi/src/main/resources/OSGI-INF/blueprint/gshell-osgi.xml b/karaf/gshell/gshell-osgi/src/main/resources/OSGI-INF/blueprint/gshell-osgi.xml
index 8dcc553..9cb7c40 100644
--- a/karaf/gshell/gshell-osgi/src/main/resources/OSGI-INF/blueprint/gshell-osgi.xml
+++ b/karaf/gshell/gshell-osgi/src/main/resources/OSGI-INF/blueprint/gshell-osgi.xml
@@ -31,7 +31,12 @@
         </command>
         <command name="osgi/list">
             <action class="org.apache.felix.karaf.gshell.osgi.ListBundles">
-                <property name="blueprintListener" ref="blueprintListener"/>
+                <property name="bundleStateListenerFactories">
+                    <list xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+                        <ref component-id="blueprintListener" />
+                        <ref component-id="springListener" />
+                    </list>
+                </property>
             </action>
         </command>
         <command name="osgi/ls">
@@ -67,7 +72,10 @@
     </command-bundle>
 
     <bean id="blueprintListener" class="org.apache.felix.karaf.gshell.osgi.BlueprintListener" />
-
     <service ref="blueprintListener" interface="org.osgi.service.blueprint.container.BlueprintListener" />
 
+    <bean id="springListener" class="org.apache.felix.karaf.gshell.osgi.SpringStateListenerFactory" destroy-method="destroy">
+        <property name="bundleContext" ref="blueprintBundleContext" />
+    </bean>
+
 </blueprint>
\ No newline at end of file