blob: 6f16ba3b0768aa73cb00b6b0bcb16edc9be79837 [file] [log] [blame]
/*
* 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.impl
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._
import org.apache.felix.servicediagnostics.JSON._
/**
* This is the ServiceDiagnostics implementation.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
class ServiceDiagnosticsImpl(val bc:BundleContext) extends ServiceDiagnostics
{
val plugins:Buffer[ServiceDiagnosticsPlugin] = Buffer()
def addPlugin(p:ServiceDiagnosticsPlugin) = plugins += p
/**
* Implements ServiceDiagnostics.notavail.
*
* 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, Set[String]] =
{
val unavail = plugins.flatMap(_.components).filterNot(_.registered)
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)
}
}
class Node(val comp:Comp, val edges:mSet[Node] = mSet[Node]()) {
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
}
/**
* Implements ServiceDiagnostics.unresolved.
*
* Returns a map of (component.name -> list(component.name)) of unresolvable services, if any.
*
* This methods first attempts to resolve all possible paths by traversing the graph
* of components dependencies, entering the graph from its outer nodes.
* Then it returns the list of unresolvable components by subtraction of the resolved components
* 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, Set[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 = plugins.flatMap(_.components).map(new Node(_))
// and connect the nodes according to component dependencies
// the 'link' method gives the direction of the link
// 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.toSet //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(_ == 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 => n.edges.isEmpty || resolved.contains(n)))
yield (node.name -> node.edges.map{ n => n.name }.toSet)).toMap
}
/**
* Implements ServiceDiagnostics.usingBundles.
*/
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.toSet)
case Nil => result
}
}
/**
* Implements ServiceDiagnostics.serviceProviders.
*/
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, 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
}
}
/**
* returns map(service name -> service reference)
*/
def allServices:Map[String,ServiceReference] =
{
val allrefs = bc.getAllServiceReferences(null, null)
if (allrefs == null) return Map()
// 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
}
}