[FELIX-4162] incorrect loop detection, [FELIX-3769] webconsole categories, [FELIX-4163] command line interface
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1499958 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/servicediagnostics/changelog.txt b/webconsole-plugins/servicediagnostics/changelog.txt
index 566f217..db86269 100644
--- a/webconsole-plugins/servicediagnostics/changelog.txt
+++ b/webconsole-plugins/servicediagnostics/changelog.txt
@@ -3,9 +3,15 @@
** Bug
* [FELIX-3898] name parsing issue on BundleDependency
+ * [FELIX-4162] incorrect loop detection
** Improvement
* [FELIX-3899] switch to Scala 2.10
+ * [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
Changes from 0.1.1 to 0.1.2
---------------------------
diff --git a/webconsole-plugins/servicediagnostics/core/pom.xml b/webconsole-plugins/servicediagnostics/core/pom.xml
index 7e24466..012e8ed 100644
--- a/webconsole-plugins/servicediagnostics/core/pom.xml
+++ b/webconsole-plugins/servicediagnostics/core/pom.xml
@@ -27,7 +27,17 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.webconsole</artifactId>
- <version>4.0.0</version>
+ <version>4.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.shell</artifactId>
+ <version>1.4.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.gogo.command</artifactId>
+ <version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
@@ -68,7 +78,8 @@
</Import-Package>
<Private-Package>
org.apache.felix.servicediagnostics.impl,
- org.apache.felix.servicediagnostics.webconsole
+ org.apache.felix.servicediagnostics.webconsole,
+ org.apache.felix.servicediagnostics.shell,
</Private-Package>
<Include-Resource>
{maven-resources}
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 893d481..68af2f6 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
+++ b/webconsole-plugins/servicediagnostics/core/src/main/resources/html/index.html
@@ -21,26 +21,33 @@
<script type="text/javascript">
<!--
-var _redraw;
-var height = 600;
-var width = 1000;
+var _redraw
+var height = 600
+var width = 1000
-DEBUG = false;
-debug = function (obj) { if(DEBUG && console) console.debug(obj); }
+DEBUG = false
+debug = function (obj) { if(DEBUG && console) console.debug(obj) }
-var services;
+function isEmpty(obj) {
+ for (var key in obj) return false
+ return true
+}
+
+var services
+var grapher
// from graph demo @ http://blog.ameisenbar.de/en/2010/03/02/dracula/
function graphUnavail(json) {
- var g = new Graph();
+ $("#legend").html("Bubbles are components, dotted squares are missing required dependencies.")
+ var g = new Graph()
- var empty = true;
- notavail = json.notavail;
+ var empty = true
+ notavail = json.notavail
for (s in notavail) {
- empty = false;
+ empty = false
for (i = 0; i < notavail[s].length; i++) {
// point unregistered service to dependency name
- var dep = notavail[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
@@ -49,107 +56,149 @@
"stroke": "gray",
"stroke-width": 2,
"stroke-dasharray": "--"
- });
+ })
}
- });
- g.addEdge(s, dep, { directed : true } );
+ })
+ g.addEdge(s, dep, { directed : true } )
}
}
- // show unresolved
- var unresolved = json.unresolved
- if (unresolved) for (s in unresolved) {
- $("#warning").html("circular dependencies detected!");
- for (i = 0; i < unresolved[s].length; i++) {
- g.addEdge(s, unresolved[s][i], { directed : true } );
- }
- }
+ // warn unresolved
+ if (json.unresolved && !isEmpty(json.unresolved))
+ $("#warning").html("circular dependencies detected! <a href='javascript:graphUnresolved()'>(show)</a>")
+ else
+ $("#warning").html("") //clear previous
if (empty) {
- $("#canvas").empty().append($("<h1>").html("Service Registry status OK: No unresolved service found."));
+ $("#canvas").empty().append($("<h1>").html("Service Registry status OK: No unresolved service found."))
}
else showGraph(g)
}
-function graphAllServices(json) {
- var g = new Graph();
+function graphUnresolved() {
+ grapher = graphUnresolved
+ $("#legend").html("Bubbles are unresolvable components linked to each other.")
+ var g = new Graph()
+ var unresolved = services.unresolved
+ for (s in unresolved) {
+ for (i = 0; i < unresolved[s].length; i++) {
+ g.addEdge(s, unresolved[s][i], { directed : true } )
+ }
+ }
+ showGraph(g)
+}
- var empty = true;
+function graphUsingServices(json) {
+ $("#legend").html("Black squares are bundles, pointing to the services they use.")
+ var g = new Graph()
+
+ var empty = true
for (s in json) {
- empty = false;
+ empty = false
for (i = 0; i < json[s].length; i++) {
// point using bundle to service name
- var bundle = json[s][i];
+ var bundle = json[s][i]
g.addNode(bundle, {
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": 2});
+ return r.rect(x-30, y-13, 62, 33, 5).attr({"fill": "#f00", "stroke-width": 2})
}
- });
- g.addEdge(bundle, s, { directed : true } );
+ })
+ g.addEdge(bundle, s, { directed : true } )
}
}
if (empty) {
- $("#canvas").empty().append($("<h1>").html("Service Registry empty: no service found."));
+ $("#canvas").empty().append($("<h1>").html("Service Registry empty: no service found."))
+ }
+ else showGraph(g)
+}
+
+function graphServiceProviders(json) {
+ $("#legend").html("Black squares are bundles, pointing to the services they provide.")
+ var g = new Graph()
+
+ var empty = true
+ for (bundle in json) {
+ empty = false
+ g.addNode(bundle, {
+ 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": 2})
+ }
+ })
+ for (i = 0; i < json[bundle].length; i++) {
+ // point bundle to service name
+ var service = json[bundle][i]
+ g.addEdge(bundle, service, { directed : true } )
+ }
+ }
+
+ if (empty) {
+ $("#canvas").empty().append($("<h1>").html("Service Registry empty: no service found."))
}
else showGraph(g)
}
function showGraph(g) {
- debug(g);
+ debug(g)
+ $("#warning").html("")
/* layout the graph using the Spring layout implementation */
- var layouter = new Graph.Layout.Spring(g);
- layouter.layout();
+ var layouter = new Graph.Layout.Spring(g)
+ layouter.layout()
/* draw the graph using the RaphaelJS draw implementation */
- $("#canvas").empty();
- var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
- renderer.draw();
+ $("#canvas").empty()
+ var renderer = new Graph.Renderer.Raphael('canvas', g, width, height)
+ renderer.draw()
_redraw = function() {
- layouter.layout();
- renderer.draw();
- };
+ layouter.layout()
+ renderer.draw()
+ }
+ $("#filterdiv").show()
}
function redraw() {
- var filter = $("#filter").val();
+ var filter = $("#filter").val()
if (filter) {
- var grep = {};
- for (s in services) if (s.indexOf(filter) >= 0) grep[s] = services[s];
- graphAllServices(grep);
+ var grep = {}
+ for (s in services) if (s.indexOf(filter) >= 0) grep[s] = services[s]
+ grapher(grep)
} else {
- graphAllServices(services);
+ grapher(services)
}
}
function loadUnavail() {
- $("#canvas").html("Loading data. Please wait...");
- $.ajax({
- url: "servicegraph/notavail",
- dataType: "json",
- success: function(json){
- services = json;
- debug("Got services");
- debug(json);
- graphUnavail(json);
- }
- });
+ var withOpt = ""
+ if ($("#optionals").attr("checked")) withOpt = "?optionals=true"
+ grapher = graphUnavail
+ loadServices("notavail"+withOpt)
}
-function loadAllServices() {
- $("#canvas").html("Loading data. Please wait...");
+function loadServiceProviders() {
+ grapher = graphServiceProviders
+ loadServices("providing")
+}
+
+function loadServiceUsers() {
+ grapher = graphUsingServices
+ loadServices("using")
+}
+
+function loadServices(cmd) {
+ $("#canvas").html("Loading data. Please wait...")
$.ajax({
- url: "servicegraph/all",
+ url: "servicegraph/"+cmd,
dataType: "json",
success: function(json){
- services = json;
- debug("Got services");
- debug(json);
- graphAllServices(json);
+ services = json
+ debug("Got services")
+ debug(json)
+ grapher(json)
}
- });
+ })
}
/* only do all this when document has finished loading (needed for RaphaelJS) */
@@ -158,20 +207,26 @@
$.getScript("servicegraph/html/js/graffle-1.3.1.js")
$.getScript("servicegraph/html/js/graph.js")
$("#actions")
- .append($("<a>").attr("href", "javascript:loadAllServices()").html("Show Service Registry"))
+ .append($("<a>").attr("href", "javascript:loadServiceProviders()").html("Show Service Providers"))
+ .append($("<span>").html(" | "))
+ .append($("<a>").attr("href", "javascript:loadServiceUsers()").html("Show Service Users"))
.append($("<span>").html(" | "))
.append($("<a>").attr("href", "javascript:loadUnavail()").html("Show Not Avail"))
-});
+ .append($("<span>").html(" "))
+ .append($("<input>").attr("id", "optionals").attr("type", "checkbox"))
+ .append($("<span>").html("Include optionals in loops"))
+})
-->
</script>
<style>
#actions a { color:black; font-weight:bold; text-decoration:none; }
#warning { color:red; font-weight:bold; }
+ #filterdiv { visibility:none; display:none; }
</style>
<div id="servicegraph">
-Filter: <input type="text" id="filter"/><button id="redraw" onclick="redraw();">redraw</button>
- <span id="actions"></span>
- <span id="warning"></span>
+<span id="actions"></span> <span id="warning"></span>
+<div id="filterdiv">Filter: <input type="text" id="filter"/><button id="redraw" onclick="redraw();">redraw</button></div>
+<div><span id="legend"></span> <span>(All nodes can be dragged around)</span></div>
<div id="canvas"></div>
</div>
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 71a320a..77e5756 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnostics.scala
@@ -32,12 +32,18 @@
/**
* returns a graph of unresolvable components (typically loops)
+ * @param optionals if true, include optional services in loop detection
*/
- def unresolved :Map[String, List[String]]
+ def unresolved(optionals:Boolean) :Map[String, List[String]]
/**
* returns a map of resolved service names to list of bundles using the service
*/
- def allServices:Map[String, List[String]]
+ def usingBundles:Map[String, List[String]]
+
+ /**
+ * returns a map of bundle names to list of provided services
+ */
+ def serviceProviders: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 4fb847a..1ae85e9 100644
--- a/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/ServiceDiagnosticsPlugin.scala
@@ -21,6 +21,10 @@
import org.osgi.framework.FrameworkUtil
import collection.JavaConversions._
+import scala.collection.mutable.{Map => mMap}
+
+import org.json.JSONObject
+
import Util._
/**
@@ -40,33 +44,40 @@
/**
* This class represents a service component.
- * @param name the service interface name
+ * @param impl the implementation class name
+ * @param service 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])
+class Comp(val impl:String, val service: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 ""}
+ override def toString = {if (registered) "[registered]" else "[unregistered]"}+shorten(impl)+"("+shorten(service)+
+ Option(props).map("#"+_.toString.hashCode).mkString+")" //properties can be too long to display :(
+
+ override def equals(o:Any) = o != null && o.getClass == getClass && {
+ val oc = o.asInstanceOf[Comp]
+ oc.impl == impl && oc.service == service && oc.props == props
+ }
}
/**
* This class represents a service dependency.
* @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
+ * @param available true if the dependency is already available in the Service Registry,
+ * or optional (irrelevant for diagnostics)
*/
-class Dependency(val name:String, val filter:String, val available:Boolean = false)
+class Dependency(val name:String, val filter:Option[String], val available:Boolean = false)
{
- private val compiled = if (filter != null && !filter.isEmpty) FrameworkUtil.createFilter(filter) else null
+ private val compiled = filter.map(FrameworkUtil.createFilter(_))
- 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))
+ def matchedBy(comp:Comp):Boolean = comp.service == name &&
+ !(compiled.isDefined && comp.props == null) && //filter and no props, doesn't match
+ (compiled.isEmpty || compiled.get.`match`(comp.props))
- override def toString = shorten(name)+{if (filter != null) filter else ""}
+ override def toString = shorten(name) + filter.mkString
}
/**
@@ -81,4 +92,10 @@
val l = classname.split('.').toList
l.map(_.take(1)).mkString(".") + l.last.drop(1)
}
+
+ /**
+ * turn the ServiceDiagnostics output into a JSON representation.
+ */
+ 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/core/src/main/scala/servicediagnostics/impl/Activator.scala b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/impl/Activator.scala
index b5ec325..c88506a 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
@@ -22,12 +22,16 @@
import org.osgi.framework.BundleContext
+import org.apache.felix.shell.Command
+import org.apache.felix.service.command.CommandProcessor
+
import org.apache.felix.dm.DependencyActivatorBase
import org.apache.felix.dm.DependencyManager
import org.apache.felix.servicediagnostics.ServiceDiagnostics
import org.apache.felix.servicediagnostics.ServiceDiagnosticsPlugin
import org.apache.felix.servicediagnostics.webconsole.WebConsolePlugin
+import org.apache.felix.servicediagnostics.shell.CLI
/**
* Activator class for the service diagnostics core implementation
@@ -69,6 +73,18 @@
.setService(classOf[ServiceDiagnostics])
.setRequired(true)
.setAutoConfig("engine")))
+
+ // register the shell command
+ dm.add(createComponent
+ .setInterface(classOf[Command].getName, new jHT[String,Any]() {{
+ put(CommandProcessor.COMMAND_FUNCTION, Array("notavail", "circular"))
+ put(CommandProcessor.COMMAND_SCOPE, "sd")
+ }})
+ .setImplementation(classOf[CLI])
+ .add(createServiceDependency
+ .setService(classOf[ServiceDiagnostics])
+ .setRequired(true)
+ .setAutoConfig("engine")))
}
override def destroy(bc:BundleContext, dm:DependencyManager) = {}
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 2985673..a9e87c1 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
@@ -43,20 +43,27 @@
override def components:List[Comp] =
{
// this involves a bit of type casting gymnastics because the underlying
- // API does not use generic types
+ // API does not use generic types and parsing of strings because the
+ // underlying API does not provide accessors
(for {
dm <- DependencyManager.getDependencyManagers.map(_.asInstanceOf[DependencyManager])
comp <- dm.getComponents.map(_.asInstanceOf[Component])
compdec = comp.asInstanceOf[ComponentDeclaration]
+ impl = comp.toString.split(" ").toList.last.takeWhile(_ != ']').trim
+ service <- compdec.getName.takeWhile(_ != '(').split(",") //multiple services for one comp
deps = compdec.getComponentDependencies
- .map(dep => new Dependency(dep.getName.takeWhile(_ != '('),
- dep.getName.dropWhile(_ != '(').trim,
+ .map(dep => new Dependency(dep.getName.takeWhile(_ != '(').trim,
+ dep.getName.dropWhile(_ != '(').trim match {
+ case "" => None
+ case f => Some(f)
+ },
dep.getState != STATE_UNAVAILABLE_REQUIRED)).toList
}
// yield Comp builds a list of Comp out of the for comprehension
- yield new Comp(compdec.getName.takeWhile(_ != '('),
+ yield new Comp(impl,
+ service.trim,
comp.getServiceProperties,
(compdec.getState != STATE_UNREGISTERED),
- deps)) toList
+ 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 e1d3e9a..a8ce4f5 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
@@ -49,11 +49,12 @@
service <- Option[Array[String]](comp.getServices).getOrElse(Array())
deps = Option[Array[Reference]](comp.getReferences).getOrElse(Array())
.map(dep => new Dependency(dep.getServiceName,
- dep.getTarget,
- dep.isSatisfied)).toList
+ Option(dep.getTarget),
+ dep.isSatisfied || dep.isOptional)).toList
}
// yield Comp builds a list of Comp out of the for comprehension
- yield new Comp(service,
+ yield new Comp(comp.getClassName.trim,
+ service.trim,
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 a2eaf6c..17903ce 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
@@ -20,12 +20,14 @@
import scala.collection.mutable.Buffer
import scala.collection.mutable.{Set => mSet}
+import scala.collection.JavaConversions._
import org.osgi.framework.BundleContext
import org.osgi.framework.ServiceReference
import org.osgi.framework.Constants.OBJECTCLASS
import org.apache.felix.servicediagnostics._
+import org.apache.felix.servicediagnostics.Util._
/**
* This is the ServiceDiagnostics implementation.
@@ -41,30 +43,34 @@
/**
* Implements ServiceDiagnostics.notavail.
*
- * This method gathers components information from all plugins
+ * This method aggregates unregistered components from all plugins
* and filters all intermediate known unregistered services
* to keep only missing "leaf" dependencies
*/
override def notavail :Map[String, List[String]] =
{
- 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
-
+ val unavail = plugins.flatMap(_.components).filterNot(_.registered)
+ unavail.foldLeft(Map[String,List[String]]()) { (map,comp) =>
+ val missing = comp.deps.filterNot { d =>
+ d.available || unavail.exists(c => d.matchedBy(c))
+ }.map(_.toString)
+ if (missing isEmpty) map else map + (shorten(comp.impl) -> missing)
+ }
}
class Node(val comp:Comp, val edges:mSet[Node] = mSet[Node]()) {
- def name = comp.toString
+ def name = comp.impl
override def toString = name + " -> " + edges.map(_.name)
+ override def equals(o:Any) = o != null && o.getClass == getClass && o.asInstanceOf[Node].comp == comp
}
+ //debug helper
+ def json(l:Iterable[Node]) = l.toList.foldLeft(new org.json.JSONArray()) { (j,n) =>
+ j.put(new org.json.JSONObject(new java.util.HashMap[String,java.util.List[String]] {{
+ put(n.name, new java.util.ArrayList[String] {{ addAll(n.edges.map(_.name)) }})
+ }}))
+ }.toString(2)
+
/**
* Implements ServiceDiagnostics.unresolved.
*
@@ -76,21 +82,26 @@
* 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 :Map[String, List[String]] =
+ override def unresolved(optionals:Boolean) :Map[String, List[String]] =
{
// first build a traversable graph from all found components 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)
+ val allnodes = plugins.flatMap(_.components).map(new Node(_))
// and connect the nodes according to component dependencies
// the 'link' method gives the direction of the link
- for ( node <- allnodes; dep <- node.comp.deps )
+ // note that all dependencies not pointing to a known component are dropped from the graph
+ for {
+ node <- allnodes
+ dep <- node.comp.deps
+ if (optionals || !dep.available)
+ }
{
allnodes.filter(n => dep.matchedBy(n.comp)).foreach(n => link(node, n) )
}
- allnodes.toList //return the graph
+ allnodes.toSet //return the graph
}
// a "forward" graph of who depends on who
@@ -121,44 +132,49 @@
// 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)
+ node <- graph.find(_ == border) // graph and triggers contain different Node instances; this uses the overriden equals methods
} yield resolve(node)).flatten.toSet
// finally filter the original graph by removing all resolved nodes
// and format the result (keeping only the names)
- (for (node <- graph.filterNot(n => resolved.contains(n)))
- yield (node.name -> node.edges.map(_.name).toList)).toMap
+ (for (node <- graph.filterNot(n => n.edges.isEmpty || resolved.contains(n)))
+ yield (node.name -> node.edges.map{ n => n.name }.toList)).toMap
}
/**
- * Implements ServiceDiagnostics.allServices.
+ * Implements ServiceDiagnostics.usingBundles.
*/
- override def allServices:Map[String,List[String]] =
+ override def usingBundles:Map[String,List[String]] =
+ allServices.foldLeft(Map[String,List[String]]()) { case (result, (name, ref)) =>
+ Option(ref.getUsingBundles).map { _.toList.map(_.toString) }.getOrElse(Nil) match {
+ case using @ h::t => result + (name -> using)
+ case Nil => result
+ }
+ }
+
+ /**
+ * Implements ServiceDiagnostics.serviceProviders.
+ */
+ override def serviceProviders:Map[String, List[String]] =
+ allServices.foldLeft(Map[String,List[String]]()) { case (result, (name, ref)) =>
+ val b = ref.getBundle.toString
+ result.updated(b, name :: result.getOrElse(b, Nil))
+ }
+
+ /**
+ * returns map(service name -> service reference)
+ */
+ def allServices:Map[String,ServiceReference] =
{
val allrefs = bc.getAllServiceReferences(null, null)
if (allrefs == null) return Map()
- // inner method used to return all the interface names a ServiceReference was registered under
- def names(ref:ServiceReference):Array[String] =
- {
- val n = ref.getProperty(OBJECTCLASS)
- if (n != null) n.asInstanceOf[Array[String]] else Array()
- }
-
- // inner method used to return all the bundles using a given ServiceReference
- def using(ref:ServiceReference):List[String] =
- {
- val u = ref.getUsingBundles
- if (u != null) u.toList.map(_ toString) else List()
- }
-
- //scan all service references to build a map of service name to list of using bundles
- (for(ref <- bc.getAllServiceReferences(null, null);
- name <- names(ref);
- u = using(ref);
- if (u.nonEmpty))
- // yield (key,value) accumulates a list of (key,value) pairs
- // the resulting list is transformed to a map and returned
- yield (name, u)) toMap
+ // scan all service references to build a map of service name to list of using bundles
+ // yield (key,value) accumulates a list of (key,value) pairs
+ // the resulting list is transformed to a map and returned
+ (for {
+ ref <- bc.getAllServiceReferences(null, null)
+ name <- Option(ref.getProperty(OBJECTCLASS)).map(_.asInstanceOf[Array[String]]).getOrElse(Array())
+ } yield (name, ref)) toMap
}
}
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
new file mode 100644
index 0000000..c3189d6
--- /dev/null
+++ b/webconsole-plugins/servicediagnostics/core/src/main/scala/servicediagnostics/shell/CLI.scala
@@ -0,0 +1,71 @@
+/*
+ * 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.servicediagnostics.shell
+
+import org.apache.felix.servicediagnostics.ServiceDiagnostics
+import org.apache.felix.servicediagnostics.Util._
+
+// old shell
+import org.apache.felix.shell.Command
+import java.io.PrintStream
+// gogo shell
+import org.apache.felix.service.command.Descriptor
+
+class CLI extends Command
+{
+ var engine:ServiceDiagnostics = _ //dependency injection. see Activator.
+
+ override def getName = "sd"
+ override def getShortDescription = "Service Diagnostics"
+ override def getUsage = "notavail|loops|using|providing"
+
+ // for gogo
+ def using = execute("sd using", System.out, System.err)
+ def providing = execute("sd providing", System.out, System.err)
+ def notavail = execute("sd notavail", System.out, System.err)
+ def loops = execute("sd loops", System.out, System.err)
+
+ // for old shell
+ override def execute(commandLine:String, out:PrintStream, err:PrintStream) = commandLine.split(" ").toList.tail match {
+ case "using"::Nil =>
+ out.println(json(engine.usingBundles).toString(2))
+ case "providing"::Nil =>
+ out.println(json(engine.serviceProviders).toString(2))
+ case "notavail"::Nil =>
+ out.println(json(engine.notavail).toString(2))
+ case "loops"::Nil => showloops(out)
+ case _ => err.println(getUsage)
+ }
+
+ def showloops(out:PrintStream) = {
+ val unresolved = engine.unresolved(false) // map(comp -> list(comp))
+ out.println(json(unresolved).toString(2))
+ def follow(n:String, stack:Set[String] = Set()) :Set[String] =
+ if (stack contains n) stack
+ else unresolved.get(n) match {
+ case None => stack
+ case Some(list) => list.toSet.flatMap { (d:String) => follow(d, stack+n) }
+ }
+ unresolved.keySet.map(follow(_)).foreach { loop =>
+ if (loop.size > 1 && unresolved(loop.last) == loop.head)
+ out.println(loop.mkString("", " -> ", " -> "+loop.head))
+ }
+ }
+
+}
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 2aaa6f4..34d181f 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
@@ -18,9 +18,6 @@
*/
package org.apache.felix.servicediagnostics.webconsole
-import scala.collection.JavaConversions._
-import scala.collection.mutable.{Map => mMap}
-
import java.io.PrintStream
import javax.servlet.http._
@@ -31,6 +28,7 @@
import org.apache.felix.webconsole.SimpleWebConsolePlugin
import org.apache.felix.servicediagnostics.ServiceDiagnostics
+import org.apache.felix.servicediagnostics.Util._
/**
* This is the Apache Felix WebConsolePlugin implementation.
@@ -38,8 +36,7 @@
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
-//class WebConsolePlugin extends SimpleWebConsolePlugin("servicegraph", "Service Graph", "OSGi", Array[String]())
-class WebConsolePlugin extends SimpleWebConsolePlugin("servicegraph", "Service Graph", Array[String]())
+class WebConsolePlugin extends SimpleWebConsolePlugin("servicegraph", "Service Graph", "OSGi", Array[String]())
{
var engine:ServiceDiagnostics = _ //dependency injection. see Activator.
@@ -57,16 +54,13 @@
*/
override def doGet(req:HttpServletRequest, resp:HttpServletResponse) =
req.getPathInfo match {
- case "/servicegraph/all" => resp.getWriter.println(json(engine.allServices))
+ case "/servicegraph/using" => resp.getWriter.println(json(engine.usingBundles))
+ case "/servicegraph/providing" => resp.getWriter.println(json(engine.serviceProviders))
case "/servicegraph/notavail" => resp.getWriter.println(new JSONObject()
.put("notavail", json(engine.notavail))
- .put("unresolved", json(engine.unresolved)))
+ .put("unresolved",
+ json(engine.unresolved(
+ Option(req.getParameter("optionals")).isDefined))))
case x => super.doGet(req, resp)
}
-
- /**
- * 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/run.sh b/webconsole-plugins/servicediagnostics/run.sh
index 7c46988..fbb03d0 100755
--- a/webconsole-plugins/servicediagnostics/run.sh
+++ b/webconsole-plugins/servicediagnostics/run.sh
@@ -1,5 +1,5 @@
REPO=$HOME/.m2/repository
-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
+SCALA=$REPO/org/apache/servicemix/bundles/org.apache.servicemix.bundles.scala-library/2.10.0/org.apache.servicemix.bundles.scala-library-2.10.0.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 \
@@ -12,7 +12,7 @@
$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/4.0.0/org.apache.felix.webconsole-4.0.0.jar\
+ $REPO/org/apache/felix/org.apache.felix.webconsole/4.2.0/org.apache.felix.webconsole-4.2.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\
diff --git a/webconsole-plugins/servicediagnostics/sample/pom.xml b/webconsole-plugins/servicediagnostics/sample/pom.xml
index 64e638a..aed554b 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</version>
+ <version>0.1.3-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
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 d0c3a18..c891927 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
@@ -154,7 +154,6 @@
def start = try
{
println("unavail="+diagnostics.notavail)
- println("all="+diagnostics.allServices)
}
catch
{