complete new implementation with support for service properties and detection of circular dependencies


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1406753 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/servicediagnostics/README.txt b/webconsole-plugins/servicediagnostics/README.txt
index 7a6d23b..59a214b 100644
--- a/webconsole-plugins/servicediagnostics/README.txt
+++ b/webconsole-plugins/servicediagnostics/README.txt
@@ -1,7 +1,7 @@
 OSGi Service Diagnostics and WebConsole Plugin
 ==============================================
 
-This projects aims at easing diagnostics of OSGi services and finding about missing dependencies.
+This projects aims at easing diagnostics of OSGi services and finding about missing and/or circular dependencies.
 
 Typically in a large system with many cascading dependencies managed by different trackers such as DeclarativeService, DependencyManager or others, tracking the root cause of a top level service not being started can become very cumbersome. When building service oriented architectures, it is often the case that a single missing requirement will lock a full stack of services, but to find that one requirement is like finding a needle in a haystack!
 
@@ -48,5 +48,4 @@
 Issues & TODOs:
 ===============
 * use of JSONObject in Servlet.scala is a bit awkward, but Scala's native json is still incomplete.. 
-* no support for service properties; needs to be added.
 * no support for iPojo, Blueprint, basic ServiceTrackers... more plugins could be developed. I only wrote the ones i'm using.
diff --git a/webconsole-plugins/servicediagnostics/core/pom.xml b/webconsole-plugins/servicediagnostics/core/pom.xml
index ead80eb..6760cce 100644
--- a/webconsole-plugins/servicediagnostics/core/pom.xml
+++ b/webconsole-plugins/servicediagnostics/core/pom.xml
@@ -27,7 +27,7 @@
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.webconsole</artifactId>
-      <version>3.1.8</version>
+      <version>4.0.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.felix</groupId>
@@ -71,11 +71,8 @@
               org.apache.felix.servicediagnostics.webconsole
             </Private-Package>
             <Include-Resource>
-              {maven-resources}, json-20090211.jar, scala-library-2.9.1.jar
+              {maven-resources}
             </Include-Resource>
-            <Bundle-ClassPath>
-              ., json-20090211.jar, scala-library-2.9.1.jar
-            </Bundle-ClassPath>
           </instructions>
         </configuration>
       </plugin>
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html b/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
index 9581ebf..85a2f84 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
+++ b/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
@@ -41,11 +41,12 @@
   var g = new Graph();
 
   var empty = true;
-  for (s in json) {
+  notavail = json.notavail;
+  for (s in notavail) {
     empty = false;
-    for (i = 0; i < json[s].length; i++) {
+    for (i = 0; i < notavail[s].length; i++) {
       // point unregistered service to dependency name
-      var dep = json[s][i];
+      var dep = notavail[s][i];
       g.addNode(dep, {
         getShape : function(r,x,y) {
           // create a dashed square shape to differentiate the missing dependency
@@ -60,6 +61,14 @@
       g.addEdge(s, dep, { directed : true } );
     }
   }
+  // show unresolved
+  var unresolved = json.unresolved
+  if (unresolved) for (s in unresolved) {
+    $("#warning").html("unresolvable components detected!");
+    for (i = 0; i < unresolved[s].length; i++) {
+      g.addEdge(s, unresolved[s][i], { directed : true } );
+    }
+  }
 
   if (empty) {
     $("#canvas").empty().append($("<h1>").html("Service Registry status OK: No unresolved service found."));
@@ -152,17 +161,22 @@
 /* only do all this when document has finished loading (needed for RaphaelJS) */
 $(document).ready(function(){
   $("#actions")
-  	.append($("<a>").attr("href", "javascript:loadAllServices()").html("All"))
+  	.append($("<a>").attr("href", "javascript:loadAllServices()").html("Show Service Registry"))
   	.append($("<span>").html("&nbsp;|&nbsp;"))
-  	.append($("<a>").attr("href", "javascript:loadUnavail()").html("Unavail"))
+  	.append($("<a>").attr("href", "javascript:loadUnavail()").html("Show Not Avail"))
 });
 
 -->
     </script>
+    <style>
+      #actions a { color:black; font-weight:bold; text-decoration:none; }
+      #warning { color:red; font-weight:bold; }
+    </style>
 </header>
 <body>
 Filter:&nbsp;<input type="text" id="filter"/><button id="redraw" onclick="redraw();">redraw</button>
 &nbsp;&nbsp;<span id="actions"></span>
+&nbsp;&nbsp;<span id="warning"></span>
 <div id="canvas"></div>
 </body>
 </html>
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
index 406c5c0..71a320a 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
@@ -28,10 +28,16 @@
     /**
      * returns a map of component name to list of leaf unresolved dependencies.
      */
-    def notavail:Map[String, List[Dependency]]
+    def notavail:Map[String, List[String]]
+
+    /**
+     * returns a graph of unresolvable components (typically loops)
+     */
+    def unresolved :Map[String, List[String]]
 
     /**
      * returns a map of resolved service names to list of bundles using the service
      */
     def allServices:Map[String, List[String]]
 }
+
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
index 41b2444..4fb847a 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
@@ -18,29 +18,67 @@
  */
 package org.apache.felix.servicediagnostics
 
+import org.osgi.framework.FrameworkUtil
+import collection.JavaConversions._
+
+import Util._
+
 /**
  * This is the interface to be implemented by participating service injection frameworks
  * such as SCR or DependencyManager. 
- * Each plugin implementation is responsible for returning its own set of leaf unresolved dependencies.
+ * Each plugin implementation is responsible for returning its own set of components.
  *
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
 trait ServiceDiagnosticsPlugin 
 {
-    /** 
-     * returns a map of unresolved service name -&gt; list of missing deps 
-     * leafs only: should not include intermediates
-     * @return the unresolved service names
+    /**
+     * returns a list of all known components for this plugin
      */
-    def getUnresolvedDependencies:Map[String, List[Dependency]] 
+    def components:List[Comp]
+}
+
+/**
+ * This class represents a service component.
+ * @param name the service interface name 
+ *   (use different instances for objects registering multiple services)
+ * @param props the service properties
+ * @param registered true if the component is already registered in the Service Registry
+ * @param deps the list of declared dependencies
+ */
+class Comp(val name:String, val props:java.util.Dictionary[_,_], val registered:Boolean, val deps:List[Dependency])
+{
+    override def toString = {if (registered) "[registered]" else "[unregistered]"}+shorten(name)+{
+        if (props != null && !props.isEmpty) " "+props else ""}
 }
 
 /**
  * This class represents a service dependency.
- * @param name the service name
+ * @param name the service interface name
  * @param filter the optional service filter
+ * @param available true if the dependency is already available in the Service Registry
  */
-class Dependency(val name:String, val filter:String) 
+class Dependency(val name:String, val filter:String, val available:Boolean = false) 
 {
-    override def toString = name//+"("+filter+")" FIXME
+    private val compiled = if (filter != null && !filter.isEmpty) FrameworkUtil.createFilter(filter) else null
+
+    def matchedBy(comp:Comp):Boolean = comp.name == name && 
+        !(compiled != null && comp.props == null) && //filter and no props, doesn't match
+        (compiled == null || compiled.`match`(comp.props))
+    
+    override def toString = shorten(name)+{if (filter != null) filter else ""}
+}
+
+/** 
+ * utility methods
+ */
+object Util
+{
+    /**
+     * shorten "org.apache.felix.servicediagnostics.ServiceDiagnostics" to "o.a.f.s.ServiceDiagnostics"
+     */
+    def shorten(classname:String) :String = { 
+        val l = classname.split('.').toList
+        l.map(_.take(1)).mkString(".") + l.last.drop(1)
+    }
 }
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/Activator.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/Activator.scala
index 786d93b..e1ac15a 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/Activator.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/Activator.scala
@@ -18,8 +18,7 @@
  */
 package org.apache.felix.servicediagnostics.impl
 
-import scala.collection.mutable.Map
-import scala.collection.JavaConversions._
+import java.util. { Hashtable => jHT }
 
 import javax.servlet.http.HttpServlet
 
@@ -78,7 +77,9 @@
 
         // and the webconsole plugin itself
         dm.add(createComponent
-            .setInterface(classOf[javax.servlet.Servlet].getName, Map("felix.webconsole.label" -> "servicegraph"))
+            .setInterface(classOf[javax.servlet.Servlet].getName, new jHT[String,String]() {{
+                  put("felix.webconsole.label", "servicegraph")
+              }})
             .setImplementation(classOf[WebConsolePlugin]))
     }
 
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DMNotAvail.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DMNotAvail.scala
index cccb04a..282e752 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DMNotAvail.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DMNotAvail.scala
@@ -25,6 +25,7 @@
 import org.osgi.framework.ServiceReference
 
 import org.apache.felix.dm.DependencyManager
+import org.apache.felix.dm.Component
 import org.apache.felix.dm.ComponentDeclaration
 import org.apache.felix.dm.ComponentDeclaration._
 import org.apache.felix.dm.ComponentDependencyDeclaration
@@ -39,42 +40,23 @@
  */
 class DMNotAvail(val bc:BundleContext) extends ServiceDiagnosticsPlugin 
 {
-
-    /**
-     * implements ServiceDiagnosticsPlugin.getUnresolvedDependencies
-     */
-    override def getUnresolvedDependencies:Map[String, List[Dependency]] = 
+    override def components:List[Comp] = 
     {
-        // build a map(name -> comp) of all ComponentDeclarations known to DM, from each DependencyManager instance
-        val comps:Map[String, ComponentDeclaration] = 
-        {
-            (for(dm <- DependencyManager.getDependencyManagers;
-                comp <- dm.asInstanceOf[DependencyManager].getComponents; 
-                cd = comp.asInstanceOf[ComponentDeclaration]) 
-                // yield (k,v) builds a list of (k,v) pairs, the resulting list is then turned into a map
-                // notice that toMap applies to the complete for-comprehension block
-                // k is the ComponentDeclaration name stripped of its filter part: everything before the '('
-                yield (cd.getName.takeWhile(_ != '('), cd)) toMap
-        }
-
-        val compNames = comps.keySet
-
-        // build and return a map(name -> List(unavail)) of unavailable dependencies:
-        // filter out registered services from the known ComponentDeclarations
-        (for(kv <- comps.filter(kv => kv._2.getState == STATE_UNREGISTERED);
-            // kv._1 is the name, kv._2 is the component
-            // here, we lookup the component's list of dependencies
-            // filtering out the ones available and the ones already known to DM, to keep only leafs
-            // (view/force added for performance)
-            unavail = kv._2.getComponentDependencies.view
-                .filter(dep => dep.getState == STATE_UNAVAILABLE_REQUIRED)
-                .filterNot(dep => compNames.contains(dep.getName))
-                .map(dep => new Dependency(dep.getName, ""/*dep.getFilter*/)).force.toList;
-            if (unavail nonEmpty)) //this 'if' is part of the for comprehension
-            // again yield (k,v) builds a list of (k,v), the final result is then turned into a map
-            // notice that toMap applies to the complete for-comprehension block
-            // toMap being the last instruction, the resulting map is the return value of the method
-            yield (kv._1, unavail)) toMap
+        // this involves a bit of type casting gymnastics because the underlying 
+        // API does not use generic types
+        (for {
+            dm <- DependencyManager.getDependencyManagers.map(_.asInstanceOf[DependencyManager])
+            comp <- dm.getComponents.map(_.asInstanceOf[Component])
+            compdec = comp.asInstanceOf[ComponentDeclaration]
+            deps = compdec.getComponentDependencies
+                          .map(dep => new Dependency(dep.getName.takeWhile(_ != ' '), 
+                                           dep.getName.dropWhile(_ != ' ').trim,
+                                           dep.getState != STATE_UNAVAILABLE_REQUIRED)).toList
+          }
+            // yield Comp builds a list of Comp out of the for comprehension
+            yield new Comp(compdec.getName.takeWhile(_ != '('), 
+                           comp.getServiceProperties,
+                           (compdec.getState != STATE_UNREGISTERED),
+                           deps)) toList
     }
-
 }
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DSNotAvail.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DSNotAvail.scala
index 80101ed..f29885c 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DSNotAvail.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/DSNotAvail.scala
@@ -36,30 +36,25 @@
 
     var scrService:ScrService = _ //dependency injection
 
-    /**
-     * implements ServiceDiagnosticsPlugin.getUnresolvedDependencies.
-     */
-    override def getUnresolvedDependencies:Map[String, List[Dependency]] = 
+    def components:List[Comp] = 
     {
-        /*
-         * Same algorithm as DMNotAvail with the SCR API. 
-         * Please refer to DMNotAvail for comments.
-         */
-        val comps:Map[String, Component] = 
-            (for (c <- Option(scrService.getComponents).flatten;
-            s <- Option(c.getServices).flatten)
-        yield (s, c)).toMap
-
-        val compNames = comps.keySet
-
-        (for (kv <- comps.filter(kv => kv._2.getState == Component.STATE_UNSATISFIED);
-            unavail = kv._2.getReferences.view
-                .filterNot(_ isSatisfied)
-                .filterNot(_ isOptional)
-                .filterNot(ref => compNames.contains(ref.getName))
-                .map(ref => new Dependency(ref.getServiceName, ref.getTarget)).force.toList;
-            if (unavail nonEmpty))
-            yield (kv._1, unavail)).toMap
+        // this involves a bit of type casting gymnastics because the underlying 
+        // API uses mutables and no generic types
+        // Option is used to avoid null pointers
+        (for {
+            comp <- Option(scrService.getComponents).flatten.map(_.asInstanceOf[Component])
+            service <- Option(comp.getServices).flatten
+            deps = Option(comp.getReferences).flatten
+                          .filterNot(_.isOptional)
+                          .map(dep => new Dependency(dep.getServiceName,
+                                                     dep.getTarget,
+                                                     dep.isSatisfied)).toList
+          }
+            // yield Comp builds a list of Comp out of the for comprehension
+            yield new Comp(service,
+                           comp.getProperties,
+                           comp.getState != Component.STATE_UNSATISFIED,
+                           deps)) toList
     }
 }
 
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/ServiceDiagnosticsImpl.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/ServiceDiagnosticsImpl.scala
index e4073af..00be218 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/ServiceDiagnosticsImpl.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/ServiceDiagnosticsImpl.scala
@@ -19,6 +19,7 @@
 package org.apache.felix.servicediagnostics.impl
 
 import scala.collection.mutable.Buffer
+import scala.collection.mutable.{Set => mSet}
 
 import org.osgi.framework.BundleContext
 import org.osgi.framework.ServiceReference
@@ -40,18 +41,85 @@
     /**
      * Implements ServiceDiagnostics.notavail.
      * 
-     * This method gathers "notavail" information from all plugins
-     * and performs the final merge by removing known unregistered services
+     * This method gathers components information from all plugins
+     * and filters all intermediate known unregistered services
+     * to keep only missing "leaf" dependencies
      */
-    override def notavail = 
+    override def notavail :Map[String, List[String]] = 
     {
-        // merge all notavails from plugins 
-        // (kv stands for each key/value pair in a map)
-        val merged = (for(p <- plugins; kv <- p.getUnresolvedDependencies) yield kv) toMap
+        val unavail :List[Comp] = for {
+                          plugin <- plugins.toList
+                          comp <- plugin.components
+                          if (! comp.registered)
+                      } yield comp
+        (for {
+            comp <- unavail
+            dep <- comp.deps.filterNot(_.available)
+            if (! unavail.exists(c => dep.matchedBy(c)))
+        } yield comp.toString -> comp.deps.filterNot(_.available).map(_.toString) ) toMap
 
-        // remove remaining intermediates. ex: unresolved in DS -> unavailable in DM
-        // and return the resulting map
-        (for(kv <- merged; dep <- kv._2; if (merged.get(dep.name) isEmpty)) yield kv) toMap
+    }
+    
+    class Node(val comp:Comp, val edges:mSet[Node] = mSet[Node]()) {
+      def name = comp.toString
+      override def toString = name + " -> " + edges.map(_.name)
+    }
+
+    /**
+     * returns a map of (component.name -> list(component.name)) of unresolvable services, if any
+     */
+    override def unresolved :Map[String, List[String]] = 
+    {
+        // first build a traversable graph from all found component and dependencies
+        def buildGraph(link:(Node,Node)=>Unit) = {
+            // concatenate component nodes from all plugins
+            val allnodes = for ( p <- plugins; comp <- p.components ) yield new Node(comp)
+
+            // and connect the nodes according to component dependencies
+            // the 'link' method gives the direction of the link
+            for ( node <- allnodes; dep <- node.comp.deps )
+            {
+                allnodes.filter(n => dep.matchedBy(n.comp)).foreach(n => link(node, n) )
+            }
+
+            allnodes.toList //return the graph
+        }
+
+        // a "forward" graph of who depends on who
+        val graph = buildGraph((n1,n2) => n1.edges += n2)
+        // and the reverse graph of who "triggers" who
+        val triggers = buildGraph((n1,n2) => n2.edges += n1)
+
+        // recursive helper method used to traverse the graph and detect loops
+        def resolve(node:Node, visited:List[Node] = Nil) :List[Node] = 
+        {
+            // if a node has no dependency, it is resolved
+            if (node.edges isEmpty) node::visited
+            else // replace ("map") each dependency with its resolution
+            {
+                val resolved = node.edges.map { e => 
+                    if (visited contains e) 
+                    { 
+                        println("!!!LOOP {"+node.name+" -> "+e+"} in "+visited)
+                        Nil // return an empty list
+                    } 
+                    else resolve(e, node::visited)
+                }
+                if (resolved.contains(Nil)) Nil // there were some loops; resolution failed
+                else resolved.flatten.toList
+            }
+        }
+
+        // now traverse the graph starting from border nodes (nodes not pointed by anyone)
+        val resolved:Set[Node] = (for { 
+            border <- triggers filter (_.edges.size == 0)
+            node <- graph.find(_.name == border.name)
+        } yield resolve(node)).flatten.toSet
+
+        // finally filter the original graph by removing all resolved nodes
+        // and format the result
+        (for (node <- graph.filterNot(n => resolved.contains(n)))
+          yield (node.name -> node.edges.map(_.name).toList)).toMap
     }
 
     /**
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/Servlet.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/Servlet.scala
index c26a3d7..2098669 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/Servlet.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/Servlet.scala
@@ -25,6 +25,7 @@
 import javax.servlet.http._
 
 import org.json.JSONObject
+import org.json.JSONArray
 
 import org.osgi.service.http.HttpContext
 import org.osgi.service.http.HttpService
@@ -55,18 +56,20 @@
     override def service(req:HttpServletRequest, resp:HttpServletResponse) = 
     {
         val cmd = req.getPathInfo
-        if(cmd.endsWith("all")) reply(resp, engine.allServices)
-        else if(cmd.endsWith("notavail")) reply(resp, engine.notavail)
+        if(cmd.endsWith("all")) reply(resp, json(engine.allServices))
+        else if(cmd.endsWith("notavail")) reply(resp, new JSONObject()
+          .put("notavail", json(engine.notavail))
+          .put("unresolved", json(engine.unresolved)))
         else println("Unrecognized cmd: "+cmd)
     }
 
+    private def reply(resp:HttpServletResponse, json:JSONObject) = 
+      new PrintStream(resp.getOutputStream, true).println(json)
+
     /** 
-    * turn the ServiceDiagnostics output into a JSON representation.
-    */
-    private def reply(resp:HttpServletResponse, map:Map[String,List[AnyRef]]) = 
-    {
-        new PrintStream(resp.getOutputStream, true).println(
-            new JSONObject(asJavaMap(mMap() ++ map.map(kv => (kv._1, asJavaList(kv._2))))))
-    }
+     * turn the ServiceDiagnostics output into a JSON representation.
+     */
+    private def json(map:Map[String,List[AnyRef]]) = 
+      new JSONObject(asJavaMap(mMap() ++ map.map(kv => (kv._1, asJavaList(kv._2)))))
 }
 
diff --git a/webconsole-plugins/servicediagnostics/parent/pom.xml b/webconsole-plugins/servicediagnostics/parent/pom.xml
index 346ab0e..c80395a 100644
--- a/webconsole-plugins/servicediagnostics/parent/pom.xml
+++ b/webconsole-plugins/servicediagnostics/parent/pom.xml
@@ -111,6 +111,15 @@
         <extensions>true</extensions>
       </plugin>
     </plugins>
+
+    <!-- added for OBR deployment -->
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+        <artifactId>wagon-ssh</artifactId>
+        <version>1.0-beta-6</version>
+      </extension>
+    </extensions>
   </build>
   
 </project>
diff --git a/webconsole-plugins/servicediagnostics/run.sh b/webconsole-plugins/servicediagnostics/run.sh
index f7b74b5..82f8f88 100755
--- a/webconsole-plugins/servicediagnostics/run.sh
+++ b/webconsole-plugins/servicediagnostics/run.sh
@@ -1,14 +1,22 @@
 REPO=$HOME/.m2/repository
-CLASSPATH=$REPO/org/scala-lang/scala-library/2.9.1/scala-library-2.9.1.jar:$REPO/org/apache/felix/org.apache.felix.main/3.2.2/org.apache.felix.main-3.2.2.jar:sample/target/servicediagnostics.sample-0.1.0-SNAPSHOT.jar
+SCALA=$REPO/org/apache/servicemix/bundles/org.apache.servicemix.bundles.scala-library/2.9.1_3/org.apache.servicemix.bundles.scala-library-2.9.1_3.jar
+CLASSPATH=$SCALA:$REPO/org/apache/felix/org.apache.felix.main/4.0.3/org.apache.felix.main-4.0.3.jar:sample/target/servicediagnostics.sample-0.1.1-SNAPSHOT.jar
 #scala 
 java -classpath $CLASSPATH org.apache.felix.servicediagnostics.sample.FelixLauncher \
-  core/target/org.apache.felix.servicediagnostics.plugin-0.1.0-SNAPSHOT.jar\
-  sample/target/servicediagnostics.sample-0.1.0-SNAPSHOT.jar\
-  $REPO/org/apache/felix/org.apache.felix.main/3.2.2/org.apache.felix.main-3.2.2.jar\
+  $SCALA\
+  core/target/org.apache.felix.servicediagnostics.plugin-0.1.2-SNAPSHOT.jar\
+  sample/target/servicediagnostics.sample-0.1.1-SNAPSHOT.jar\
+  $REPO/org/apache/felix/org.apache.felix.main/4.0.3/org.apache.felix.main-4.0.3.jar\
   $REPO/org/apache/felix/org.apache.felix.dependencymanager/3.0.0/org.apache.felix.dependencymanager-3.0.0.jar\
+  $REPO/org/apache/felix/org.apache.felix.dependencymanager.shell/3.0.0/org.apache.felix.dependencymanager.shell-3.0.0.jar\
   $REPO/org/apache/felix/org.apache.felix.scr/1.6.0/org.apache.felix.scr-1.6.0.jar\
   $REPO/org/osgi/org.osgi.compendium/4.2.0/org.osgi.compendium-4.2.0.jar\
   $REPO/org/apache/felix/org.apache.felix.http.jetty/2.2.0/org.apache.felix.http.jetty-2.2.0.jar\
-  $REPO/org/apache/felix/org.apache.felix.webconsole/3.1.8/org.apache.felix.webconsole-3.1.8.jar\
-  $REPO/org/apache/felix/org.apache.felix.shell/1.4.2/org.apache.felix.shell-1.4.2.jar
+  $REPO/org/apache/felix/org.apache.felix.webconsole/4.0.0/org.apache.felix.webconsole-4.0.0.jar\
+  $REPO/org/apache/felix/org.apache.felix.shell/1.4.3/org.apache.felix.shell-1.4.3.jar\
+  $REPO/org/apache/commons/com.springsource.org.apache.commons.fileupload/1.2.1/com.springsource.org.apache.commons.fileupload-1.2.1.jar\
+  $REPO/org/apache/commons/com.springsource.org.apache.commons.io/1.4.0/com.springsource.org.apache.commons.io-1.4.0.jar\
+  $REPO/org/apache/geronimo/bundles/json/20090211_1/json-20090211_1.jar\
+  $REPO/org/apache/felix/org.apache.felix.webconsole.plugins.shell/1.0.0-SNAPSHOT/org.apache.felix.webconsole.plugins.shell-1.0.0-SNAPSHOT.jar
+
  
diff --git a/webconsole-plugins/servicediagnostics/sample/pom.xml b/webconsole-plugins/servicediagnostics/sample/pom.xml
index 1929970..164e6ce 100644
--- a/webconsole-plugins/servicediagnostics/sample/pom.xml
+++ b/webconsole-plugins/servicediagnostics/sample/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>org.apache.felix</groupId>
     <artifactId>servicediagnostics.parent</artifactId>
-    <version>0.1.1-SNAPSHOT</version>
+    <version>0.1.1</version>
     <relativePath>../parent/pom.xml</relativePath>
   </parent>
 
@@ -16,17 +16,60 @@
   <packaging>bundle</packaging>
   <name>Sample Services and Launcher for Service Diagnostics</name>
 
+  <repositories>
+    <repository> 
+      <id>com.springsource.repository.bundles.release</id> 
+      <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</name> 
+      <url>http://repository.springsource.com/maven/bundles/release</url> 
+    </repository>
+    <repository> 
+      <id>com.springsource.repository.bundles.external</id> 
+      <name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name> 
+      <url>http://repository.springsource.com/maven/bundles/external</url> 
+    </repository>
+  </repositories>
+  
   <dependencies>
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>${project.groupId}.servicediagnostics.plugin</artifactId>
-      <version>0.1.0-SNAPSHOT</version>
+      <version>0.1.2-SNAPSHOT</version>
     </dependency>
     <!-- runtime dependencies. added to populate the local repository -->
+    <dependency> 
+      <groupId>org.apache.felix</groupId> 
+      <artifactId>org.apache.felix.webconsole.plugins.shell</artifactId>
+      <version>1.0.0-SNAPSHOT</version> 
+    </dependency>
+    <dependency> 
+      <groupId>org.apache.felix</groupId> 
+      <artifactId>org.apache.felix.webconsole.plugins.ds</artifactId>
+      <version>1.0.0</version> 
+    </dependency>
+    <dependency> 
+      <groupId>org.apache.commons</groupId> 
+      <artifactId>com.springsource.org.apache.commons.fileupload</artifactId> 
+      <version>1.2.1</version> 
+    </dependency>
+    <dependency> 
+      <groupId>org.apache.commons</groupId> 
+      <artifactId>com.springsource.org.apache.commons.io</artifactId> 
+      <version>1.4.0</version> 
+    </dependency>
+    <dependency> 
+      <groupId>org.apache.geronimo.bundles</groupId> 
+      <artifactId>json</artifactId> 
+      <version>20090211_1</version> 
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.dependencymanager.shell</artifactId>
+      <version>3.0.0</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.main</artifactId>
-      <version>3.2.2</version>
+      <version>4.0.3</version>
     </dependency>
     <dependency>
       <groupId>org.apache.felix</groupId>
@@ -36,13 +79,12 @@
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.shell</artifactId>
-      <version>1.4.2</version>
+      <version>1.4.3</version>
     </dependency>
     <dependency>
-      <groupId>org.scala-lang</groupId>
-      <artifactId>scala-library</artifactId>
-      <version>2.9.1</version>
-      <scope>provided</scope>
+      <groupId>org.apache.servicemix.bundles</groupId>
+      <artifactId>org.apache.servicemix.bundles.scala-library</artifactId>
+      <version>2.9.1_3</version>
     </dependency>
   </dependencies>
 
@@ -60,6 +102,7 @@
           <instructions>
             <Bundle-Category>samples</Bundle-Category>
             <Bundle-SymbolicName> ${project.artifactId} </Bundle-SymbolicName>
+            <Bundle-Activator> org.apache.felix.servicediagnostics.sample.TestDM </Bundle-Activator>
             <Service-Component> * </Service-Component>
             <Private-Package>
               org.apache.felix.servicediagnostics.sample
@@ -68,11 +111,8 @@
               !aQute.bnd.annotation.component,!org.apache.felix.main,sun.misc*;resolution:=optional,*
             </Import-Package>
             <Include-Resource>
-              {maven-resources},scala-library-2.9.1.jar
+              {maven-resources}
             </Include-Resource>
-            <Bundle-ClassPath>
-              ., scala-library-2.9.1.jar
-            </Bundle-ClassPath>
           </instructions>
         </configuration>
       </plugin>
diff --git a/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDM.scala b/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDM.scala
index d56b364..f0dde6b 100644
--- a/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDM.scala
+++ b/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDM.scala
@@ -23,8 +23,11 @@
 import org.apache.felix.dm.DependencyManager
 import org.apache.felix.servicediagnostics.ServiceDiagnostics
 
+import java.util. { Hashtable => jHT }
+
 /**
  * This class is a basic DependencyManager based demonstration
+ * 
  *
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
@@ -48,11 +51,16 @@
                 .setRequired(true)))
 
         // initialize some sample services for testing purpose (see also TestDS)
+
+        //
+        // test1:
+        //  DM1 -> DS1 -> DM2 -> Unavail
+
         dm.add(createComponent
             .setInterface(classOf[DM1].getName, null)
             .setImplementation(classOf[DM1])
             .add(createServiceDependency
-                .setService(classOf[DM2])
+                .setService(classOf[DS1])
                 .setAutoConfig(false)
                 .setCallbacks(null, null, null)
                 .setRequired(true)))
@@ -61,16 +69,65 @@
             .setInterface(classOf[DM2].getName, null)
             .setImplementation(classOf[DM2])
             .add(createServiceDependency
-                .setService(classOf[DM3])
+                .setService(classOf[Runnable]) //Unavail
+                .setAutoConfig(false)
+                .setCallbacks(null, null, null)
+                .setRequired(true)))
+
+        // test2: properties
+        // DMP(0,1) -> DSP(0) -> DMP(2) -> Unavail
+
+        dm.add(createComponent
+            .setInterface(classOf[DMP].getName, new jHT[String,String]() {{
+              put("p", "0")
+              put("q", "1")
+            }})
+            .setImplementation(classOf[DMP])
+            .add(createServiceDependency
+                .setService(classOf[DSP], "(p=0)")
                 .setAutoConfig(false)
                 .setCallbacks(null, null, null)
                 .setRequired(true)))
 
         dm.add(createComponent
-            .setInterface(classOf[DM3].getName, null)
-            .setImplementation(classOf[DM3])
+            .setInterface(classOf[DMP].getName, new jHT[String,String]() {{
+              put("p", "2")
+            }})
+            .setImplementation(classOf[DMP])
             .add(createServiceDependency
-                .setService(classOf[Runnable]) //missing dependency
+                .setService(classOf[Runnable]) // Unavail
+                .setAutoConfig(false)
+                .setCallbacks(null, null, null)
+                .setRequired(true)))
+
+        // test3: loop
+        // DML1 -> DSL1 -> DML2 -> DML1
+
+        dm.add(createComponent
+            .setInterface(classOf[DML1].getName, new jHT[String,String]() {{
+              put("p", "1")
+              put("q", "1")
+            }})
+            .setImplementation(classOf[DML1])
+            .add(createServiceDependency
+                .setService(classOf[DSL1], "(q=1)")
+                .setAutoConfig(false)
+                .setCallbacks(null, null, null)
+                .setRequired(true)))
+
+        dm.add(createComponent
+            .setInterface(classOf[DML2].getName, new jHT[String,String]() {{
+              put("p", "2")
+              put("q", "2")
+            }})
+            .setImplementation(classOf[DML2])
+            .add(createServiceDependency
+                .setService(classOf[DML1]) //Loop
+                .setAutoConfig(false)
+                .setCallbacks(null, null, null)
+                .setRequired(true))
+            .add(createServiceDependency
+                .setService(classOf[TestDS]) //Available with no deps
                 .setAutoConfig(false)
                 .setCallbacks(null, null, null)
                 .setRequired(true)))
@@ -78,14 +135,20 @@
 
     override def destroy(bc:BundleContext, dm:DependencyManager) = {}
 
-    def start = 
+    def start = try 
     {
         println("unavail="+diagnostics.notavail)
         println("all="+diagnostics.allServices)
     }
+    catch 
+    {
+      case (e:Exception) => e.printStackTrace
+    }
 }
 
 //sample service classes
 class DM1
 class DM2
-class DM3
+class DMP
+class DML1
+class DML2
diff --git a/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDS.scala b/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDS.scala
index 57adf9b..1a830cf 100644
--- a/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDS.scala
+++ b/webconsole-plugins/servicediagnostics/sample/src/main/scala/servicediagnostics/sample/TestDS.scala
@@ -27,17 +27,36 @@
  */
 @Component(immediate=true, provide=Array(classOf[TestDS])) class TestDS 
 
-@Component(provide=Array(classOf[DS1])) class DS1 
+//  DM1 -> DS1 -> DM2 -> Unavail
+@Component(provide=Array(classOf[DS1])) 
+class DS1 
 {
-    @Reference def setDS2(s:DS2) = {}
+    @Reference def bind(s:DM2) = {}
 }
 
-@Component(provide=Array(classOf[DS2])) class DS2 
+// DMP(0,1) -> DSP(0) -> DMP(2) -> Unavail
+@Component(provide=Array(classOf[DSP]), properties=Array("p=0")) 
+class DSP
 {
-    @Reference def setDM3(s:DM3) = {}
+    @Reference(target="(p=2)") def bind(s:DMP) = {}
 }
 
-@Component(provide=Array(classOf[DS3])) class DS3 
+// DML1 -> DSL1 -> DML2 -> DML1
+@Component(provide=Array(classOf[DSL1]), properties=Array("p=0","q=1")) 
+class DSL1 
 {
-    @Reference def setMap(s:java.util.Map[String,String]) = {} //not available
+    @Reference(target="(p=2)") def bind(s:DML2) = {}
 }
+
+/* DSL2 -> DML2 -x-> DSL3 -> DSL2
+@Component(provide=Array(classOf[DSL2]), properties=Array("p=1","q=0")) 
+class DSL2 
+{
+    @Reference(target="(p=2)") def bind(s:DML2) = {}
+}
+@Component(provide=Array(classOf[DSL3]), properties=Array("p=1","q=0")) 
+class DSL3 
+{
+    @Reference(target="(q=0)") def bind(s:DSL2) = {}
+}*/
+