added view of bundle to bundle wiring via services

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1500028 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/servicediagnostics/changelog.txt b/webconsole-plugins/servicediagnostics/changelog.txt
index db86269..fffa2b4 100644
--- a/webconsole-plugins/servicediagnostics/changelog.txt
+++ b/webconsole-plugins/servicediagnostics/changelog.txt
@@ -10,8 +10,9 @@
   * [FELIX-3769] webconsole categories
   * [FELIX-4163] command line interface
   * circular dependencies shown on a separate graph with or wihtout optionals
-  * show both using bundles and providing bundles
-  * show components as implementations ans services as interfaces
+  * new visualisations of the service registry: service providers, using
+  bundles, and bundles wiring via services
+  * show components as implementations and services as interfaces
 
 Changes from 0.1.1 to 0.1.2
 ---------------------------
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 68af2f6..adc416f 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
+++ b/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
@@ -139,6 +139,38 @@
   else showGraph(g)
 }
 
+function graphB2B(json) {
+  $("#legend").html("Black squares are bundles, pointing to the bundles they use for their services.")
+  var g = new Graph()
+
+  var empty = true
+  for (provider in json) {
+    empty = false
+    g.addNode(provider, {
+      getShape : function(r,x,y) {
+        // create a square shape to differentiate bundles from services
+        return r.rect(x-30, y-13, 62, 33, 5).attr({"fill": "#f00", "stroke-width": 1})
+      }
+    })
+    for (i = 0; i < json[provider].length; i++) {
+      // point using bundle to provider bundle
+      var user = json[provider][i]
+      g.addNode(user, {
+        getShape : function(r,x,y) {
+          // create a square shape to differentiate bundles from services
+          return r.rect(x-30, y-13, 62, 33, 5).attr({"stroke-width": 2})
+        }
+      })
+      g.addEdge(user, provider, { directed : true } )
+    }
+  }
+
+  if (empty) {
+    $("#canvas").empty().append($("<h1>").html("Service Registry empty: no service found."))
+  }
+  else showGraph(g)
+}
+
 function showGraph(g) {
     debug(g)
     $("#warning").html("")
@@ -187,6 +219,11 @@
   loadServices("using")
 }
 
+function loadB2B() {
+  grapher = graphB2B
+  loadServices("b2b")
+}
+
 function loadServices(cmd) {
   $("#canvas").html("Loading data. Please wait...")
   $.ajax({
@@ -211,6 +248,8 @@
   	.append($("<span>").html("&nbsp;|&nbsp;"))
   	.append($("<a>").attr("href", "javascript:loadServiceUsers()").html("Show Service Users"))
   	.append($("<span>").html("&nbsp;|&nbsp;"))
+  	.append($("<a>").attr("href", "javascript:loadB2B()").html("Show Bundles Dependencies"))
+  	.append($("<span>").html("&nbsp;|&nbsp;"))
   	.append($("<a>").attr("href", "javascript:loadUnavail()").html("Show Not Avail"))
   	.append($("<span>").html("&nbsp;"))
         .append($("<input>").attr("id", "optionals").attr("type", "checkbox"))
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 77e5756..85eb4af 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
@@ -28,22 +28,28 @@
     /**
      * returns a map of component name to list of leaf unresolved dependencies.
      */
-    def notavail:Map[String, List[String]]
+    def notavail:Map[String, Set[String]]
 
     /**
      * returns a graph of unresolvable components (typically loops)
      * @param optionals if true, include optional services in loop detection
      */
-    def unresolved(optionals:Boolean) :Map[String, List[String]]
+    def unresolved(optionals:Boolean) :Map[String, Set[String]]
 
     /**
      * returns a map of resolved service names to list of bundles using the service
      */
-    def usingBundles:Map[String, List[String]]
+    def usingBundles:Map[String, Set[String]]
 
     /**
      * returns a map of bundle names to list of provided services
      */
-    def serviceProviders:Map[String, List[String]]
+    def serviceProviders:Map[String, Set[String]]
+
+    /**
+     * shows service links between bundles
+     * returns a map of bundle names to list of bundles using its services
+     */
+    def b2b:Map[String, Set[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 3a33dca..7a22cac 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
@@ -96,6 +96,6 @@
     /** 
      * turn the ServiceDiagnostics output into a JSON representation.
      */
-    def json(map:Map[String,List[String]]) = 
-      new JSONObject(asJavaMap(mMap() ++ map.map(kv => (kv._1, asJavaList(kv._2)))))
+    def json(map:Map[String,Set[String]]) = 
+      new JSONObject(asJavaMap(mMap() ++ map.map(kv => (kv._1, asJavaList(kv._2.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 17903ce..5c27780 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
@@ -47,11 +47,11 @@
      * and filters all intermediate known unregistered services
      * to keep only missing "leaf" dependencies
      */
-    override def notavail :Map[String, List[String]] = 
+    override def notavail :Map[String, Set[String]] = 
     {
         val unavail = plugins.flatMap(_.components).filterNot(_.registered)
-        unavail.foldLeft(Map[String,List[String]]()) { (map,comp) =>
-            val missing = comp.deps.filterNot { d =>
+        unavail.foldLeft(Map[String,Set[String]]()) { (map,comp) =>
+            val missing = comp.deps.toSet.filterNot { d =>
                   d.available || unavail.exists(c => d.matchedBy(c))
                 }.map(_.toString) 
             if (missing isEmpty) map else map + (shorten(comp.impl) -> missing)
@@ -82,7 +82,7 @@
      * from the original graph. This is done because "perfect loops" have no border node and are 
      * therefore "invisible" to the traversing algorithm.
      */
-    override def unresolved(optionals:Boolean) :Map[String, List[String]] = 
+    override def unresolved(optionals:Boolean) :Map[String, Set[String]] = 
     {
         // first build a traversable graph from all found components and dependencies
         def buildGraph(link:(Node,Node)=>Unit) = {
@@ -138,16 +138,16 @@
         // finally filter the original graph by removing all resolved nodes
         // and format the result (keeping only the names)
         (for (node <- graph.filterNot(n => n.edges.isEmpty || resolved.contains(n)))
-          yield (node.name -> node.edges.map{ n => n.name }.toList)).toMap
+          yield (node.name -> node.edges.map{ n => n.name }.toSet)).toMap
     }
 
     /**
      * Implements ServiceDiagnostics.usingBundles.
      */
-    override def usingBundles:Map[String,List[String]] = 
-        allServices.foldLeft(Map[String,List[String]]()) { case (result, (name, ref)) =>
+    override def usingBundles:Map[String,Set[String]] = 
+        allServices.foldLeft(Map[String,Set[String]]()) { case (result, (name, ref)) =>
             Option(ref.getUsingBundles).map { _.toList.map(_.toString) }.getOrElse(Nil) match {
-                case using @ h::t => result + (name -> using)
+                case using @ h::t => result + (name -> using.toSet)
                 case Nil => result
             }
         }
@@ -155,10 +155,22 @@
     /**
      * Implements ServiceDiagnostics.serviceProviders.
      */
-    override def serviceProviders:Map[String, List[String]] = 
-        allServices.foldLeft(Map[String,List[String]]()) { case (result, (name, ref)) =>
+    override def serviceProviders:Map[String, Set[String]] = 
+        allServices.foldLeft(Map[String,Set[String]]()) { case (result, (name, ref)) =>
             val b = ref.getBundle.toString
-            result.updated(b, name :: result.getOrElse(b, Nil))
+            result.updated(b, result.getOrElse(b, Set()) + name)
+        }
+
+    override def b2b:Map[String,Set[String]] = 
+        allServices.foldLeft(Map[String,Set[String]]()) { case (result, (name, ref)) =>
+            Option(ref.getUsingBundles).map { _.toList.map(_.toString) }.getOrElse(Nil) match {
+                case using @ h::t => 
+                    val b = ref.getBundle.toString
+                    val filteredUsers = using.filter(_ != b)
+                    if (filteredUsers isEmpty) result
+                    else result.updated(b, result.getOrElse(b, Set()) ++ using)
+                case Nil => result
+            }
         }
 
     /**
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/shell/CLI.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/shell/CLI.scala
index c3189d6..a78a8a9 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/shell/CLI.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/shell/CLI.scala
@@ -33,11 +33,12 @@
 
     override def getName = "sd"
     override def getShortDescription = "Service Diagnostics"
-    override def getUsage = "notavail|loops|using|providing"
+    override def getUsage = "notavail|loops|using|providing|b2b"
 
     // for gogo
     def using = execute("sd using", System.out, System.err)
     def providing = execute("sd providing", System.out, System.err)
+    def b2b = execute("sd b2b", System.out, System.err)
     def notavail = execute("sd notavail", System.out, System.err)
     def loops = execute("sd loops", System.out, System.err)
 
@@ -47,6 +48,8 @@
             out.println(json(engine.usingBundles).toString(2))
         case "providing"::Nil => 
             out.println(json(engine.serviceProviders).toString(2))
+        case "b2b"::Nil => 
+            out.println(json(engine.b2b).toString(2))
         case "notavail"::Nil => 
             out.println(json(engine.notavail).toString(2))
         case "loops"::Nil => showloops(out)
diff --git a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/WebConsolePlugin.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/WebConsolePlugin.scala
index 34d181f..530e14c 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/WebConsolePlugin.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/webconsole/WebConsolePlugin.scala
@@ -56,6 +56,7 @@
         req.getPathInfo match {
             case "/servicegraph/using" => resp.getWriter.println(json(engine.usingBundles))
             case "/servicegraph/providing" => resp.getWriter.println(json(engine.serviceProviders))
+            case "/servicegraph/b2b" => resp.getWriter.println(json(engine.b2b))
             case "/servicegraph/notavail" => resp.getWriter.println(new JSONObject()
                                   .put("notavail", json(engine.notavail))
                                   .put("unresolved",