Committed the following contributions provided by Raluca Grigoras (many thanks):
[FELIX-4873] - Enhance DM API to get missing and circular dependencies
[FELIX-4889] - Refactor dm shell command to use the org.apache.dm.diagnostics api
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1680380 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DiagnosticsTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DiagnosticsTest.java
new file mode 100644
index 0000000..5a459d5
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DiagnosticsTest.java
@@ -0,0 +1,464 @@
+/*
+ * 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.dm.itest.api;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ConfigurationDependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.diagnostics.CircularDependency;
+import org.apache.felix.dm.diagnostics.DependencyGraph;
+import org.apache.felix.dm.diagnostics.DependencyGraph.ComponentState;
+import org.apache.felix.dm.diagnostics.DependencyGraph.DependencyState;
+import org.apache.felix.dm.diagnostics.MissingDependency;
+import org.apache.felix.dm.itest.util.TestBase;
+
+public class DiagnosticsTest extends TestBase {
+
+ // there is one TestComponent created by org.apache.felix.dm.itest.bundle
+ // this component is always registered, so we only need to take it into
+ // account when we build the graph with ALL components
+ private static final int InitialComponentCount = 1;
+
+ private boolean checkComponentCount(int expected, int count) {
+ return count == expected + InitialComponentCount;
+ }
+
+ public void testWithoutComponents() throws Exception {
+
+ DependencyGraph graph = DependencyGraph.getGraph(
+ ComponentState.ALL,
+ DependencyState.ALL);
+
+ assertTrue(checkComponentCount(0, graph.getAllComponents().size()));
+ assertTrue(graph.getAllDependencies().isEmpty());
+ }
+
+ public void testSingleComponent() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ Component component = dm.createComponent()
+ .setImplementation(Object.class);
+
+ dm.add(component);
+
+ DependencyGraph graph = DependencyGraph.getGraph(
+ ComponentState.ALL, DependencyState.ALL);
+
+ assertTrue(checkComponentCount(1, graph.getAllComponents().size()));
+ assertTrue(graph.getAllDependencies().isEmpty());
+
+ graph = DependencyGraph.getGraph(
+ ComponentState.UNREGISTERED, DependencyState.ALL_UNAVAILABLE);
+
+ assertTrue(graph.getAllComponents().isEmpty());
+ assertTrue(graph.getAllDependencies().isEmpty());
+ }
+
+ public void testServiceDependencyMissing() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ ServiceDependency serviceDependency1 = dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true);
+ ServiceDependency serviceDependency2 = dm.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true);
+
+ Component component1 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(serviceDependency1);
+ Component component2 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), null)
+ .add(serviceDependency2);
+
+ dm.add(component1);
+ dm.add(component2);
+
+ DependencyGraph graph = DependencyGraph.getGraph(
+ ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
+
+ assertEquals(2, graph.getAllComponents().size());
+ assertEquals(2, graph.getAllDependencies().size());
+
+ List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
+ assertEquals(1, missingDependencies.size());
+ assertEquals(S2.class.getName(), missingDependencies.get(0).getName());
+
+ assertTrue(graph.getMissingDependencies("configuration").isEmpty());
+ assertTrue(graph.getMissingDependencies("bundle").isEmpty());
+ assertTrue(graph.getMissingDependencies("resource").isEmpty());
+
+ }
+
+ public void testConfigurationDependencyMissing() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ ConfigurationDependency configurationDependency1 = dm.createConfigurationDependency()
+ .setPid("missing.configuration.pid");
+
+ Component component1 = dm.createComponent()
+ .setImplementation(Object.class)
+ .add(configurationDependency1);
+ m_dm.add(component1);
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
+
+ assertEquals(1, graph.getAllComponents().size());
+ assertEquals(1, graph.getAllDependencies().size());
+
+ List<MissingDependency> missingServiceDependencies = graph.getMissingDependencies("service");
+ assertTrue(missingServiceDependencies.isEmpty());
+
+ List<MissingDependency> missingConfigDependencies = graph.getMissingDependencies("configuration");
+ assertEquals(1, missingConfigDependencies.size());
+
+ MissingDependency missingConfigDependency = missingConfigDependencies.get(0);
+ assertEquals("missing.configuration.pid", missingConfigDependency.getName());
+ }
+
+ public void testProvidersWithoutProperties() throws Exception {
+ DependencyManager dm = getDM();
+
+ ServiceDependency serviceDependency1 = dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true);
+
+ Component component1 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(serviceDependency1);
+
+ Component component2 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), null);
+ Component component3 = dm.createComponent()
+ .setImplementation(S1Impl2.class)
+ .setInterface(S1.class.getName(), null);
+
+ dm.add(component1);
+ dm.add(component2);
+ dm.add(component3);
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
+
+ List<ComponentDeclaration> providers = graph.getProviders(serviceDependency1);
+ assertEquals(2, providers.size());
+ assertTrue(providers.contains(component2));
+ assertTrue(providers.contains(component3));
+ }
+
+ public void testProvidersWithProperties() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ ServiceDependency serviceDependency1 = dm.createServiceDependency()
+ .setService(S1.class, "(key=value)")
+ .setRequired(true);
+
+ Component component1 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(serviceDependency1);
+
+ Properties component2Properties = new Properties();
+ component2Properties.put("key", "value");
+ Properties component4Properties = new Properties();
+ component4Properties.put("key", "otherValue");
+
+ Component component2 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), component2Properties);
+ Component component3 = dm.createComponent()
+ .setImplementation(S1Impl2.class)
+ .setInterface(S1.class.getName(), null);
+ Component component4 = dm.createComponent()
+ .setImplementation(S1Impl3.class)
+ .setInterface(S1.class.getName(), component4Properties);
+
+ m_dm.add(component1);
+ m_dm.add(component2);
+ m_dm.add(component3);
+ m_dm.add(component4);
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
+
+ List<ComponentDeclaration> providers = graph.getProviders(serviceDependency1);
+ assertEquals(1, providers.size());
+ assertTrue(providers.contains(component2));
+ assertFalse(providers.contains(component3));
+ assertFalse(providers.contains(component4));
+
+ }
+
+ public void testCircularDependencies() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ Component component0 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true));
+
+ Component component1 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), null)
+ .add(dm.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true));
+ Component component2 = dm.createComponent()
+ .setImplementation(S2Impl1.class)
+ .setInterface(S2.class.getName(), null)
+ .add(dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true));
+
+ m_dm.add(component0);
+ m_dm.add(component1);
+ m_dm.add(component2);
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED,DependencyState.REQUIRED_UNAVAILABLE);
+ List<CircularDependency> circularDependencies = graph.getCircularDependencies();
+
+ assertEquals(1, circularDependencies.size());
+
+ List<ComponentDeclaration> circularDependencyComponents = circularDependencies.get(0).getComponents();
+ assertTrue(circularDependencyComponents.contains(component1));
+ assertTrue(circularDependencyComponents.contains(component2));
+ assertFalse(circularDependencyComponents.contains(component0));
+
+ }
+
+ public void testWithTwoProvidersOneUnavailable() {
+ DependencyManager dm = getDM();
+
+ Component component0 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true));
+ Component component1 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), null);
+ Component component2 = dm.createComponent()
+ .setImplementation(S1Impl2.class)
+ .setInterface(S1.class.getName(), null)
+ .add(dm.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true));
+
+ dm.add(component0);
+ dm.add(component1);
+ dm.add(component2);
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
+
+ assertEquals(1, graph.getAllComponents().size());
+ List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
+ assertEquals(1, missingDependencies.size());
+
+ MissingDependency missingDependency = missingDependencies.get(0);
+ assertTrue(missingDependency.getName().equals(S2.class.getName()));
+
+ }
+
+ public void testGraphsWithSeveralComponentDependencyCombinations() throws Exception {
+
+ DependencyManager dm = getDM();
+
+ Component component0 = dm.createComponent()
+ .setImplementation(C0.class)
+ .add(dm.createServiceDependency()
+ .setService(S1.class)
+ .setRequired(true))
+ .add(dm.createServiceDependency()
+ .setService(S3.class)
+ .setRequired(true))
+ .add(dm.createServiceDependency()
+ .setService(S4.class)
+ .setRequired(true));
+ Component component1 = dm.createComponent()
+ .setImplementation(C1.class)
+ .add(dm.createServiceDependency()
+ .setService(S5.class)
+ .setRequired(true))
+ .add(dm.createServiceDependency()
+ .setService(S6.class)
+ .setRequired(true))
+ .add(dm.createServiceDependency()
+ .setService(S7.class)
+ .setRequired(true));
+ Component s1Impl1 = dm.createComponent()
+ .setImplementation(S1Impl1.class)
+ .setInterface(S1.class.getName(), null)
+ .add(dm.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true));
+ Component s1Impl2 = dm.createComponent()
+ .setImplementation(S1Impl2.class)
+ .setInterface(S1.class.getName(), null);
+
+ Component s3Impl1 = dm.createComponent()
+ .setImplementation(S3Impl1.class)
+ .setInterface(S3.class.getName(), null)
+ .add(dm.createConfigurationDependency()
+ .setPid("missing.config.pid"));
+ Component s3s4Impl = dm.createComponent()
+ .setImplementation(S3S4Impl.class)
+ .setInterface( new String[] {S3.class.getName(), S4.class.getName()}, null);
+ Component s4Impl1 = dm.createComponent()
+ .setImplementation(S4Impl1.class)
+ .setInterface(S4.class.getName(), null);
+ Component s5Impl1 = dm.createComponent()
+ .setImplementation(S5Impl1.class)
+ .setInterface(S5.class.getName(), null);
+ Component s6s7Impl = dm.createComponent()
+ .setImplementation(S6S7Impl.class)
+ .setInterface( new String[] {S6.class.getName(), S7.class.getName()}, null)
+ .add(dm.createServiceDependency()
+ .setService(S8.class)
+ .setRequired(true));
+ Component s8Impl1 = dm.createComponent()
+ .setImplementation(S8Impl1.class)
+ .setInterface(S8.class.getName(), null)
+ .add(dm.createServiceDependency()
+ .setService(S6.class)
+ .setRequired(true));
+ dm.add(component0);
+ dm.add(component1);
+ dm.add(s1Impl1); dm.add(s1Impl2);
+ dm.add(s3Impl1); dm.add(s3s4Impl);
+ dm.add(s4Impl1); dm.add(s5Impl1);
+ dm.add(s6s7Impl); dm.add(s8Impl1);
+
+
+ // graph containing all components and dependencies
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
+
+ List<ComponentDeclaration> allComponents = graph.getAllComponents();
+ assertTrue(checkComponentCount(10, allComponents.size()));
+
+ List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
+ assertEquals(1, missingDependencies.size());
+
+ missingDependencies = graph.getMissingDependencies("configuration");
+ assertEquals(1, missingDependencies.size());
+
+ List<CircularDependency> circularDependencies = graph.getCircularDependencies();
+ assertEquals(1, circularDependencies.size());
+ CircularDependency circularDependency = circularDependencies.get(0);
+
+ assertEquals(3, circularDependency.getComponents().size());
+ assertTrue(circularDependency.getComponents().contains(s6s7Impl));
+ assertTrue(circularDependency.getComponents().contains(s8Impl1));
+
+ // graph containing unregistered components and unavailable required dependencies
+ graph = null;
+ graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
+
+ List<ComponentDeclaration> unregComponents = graph.getAllComponents();
+ assertEquals(5, unregComponents.size());
+ assertTrue(unregComponents.contains(s1Impl1));
+ assertTrue(unregComponents.contains(s3Impl1));
+ assertTrue(unregComponents.contains(component1));
+ assertTrue(unregComponents.contains(s6s7Impl));
+ assertTrue(unregComponents.contains(s8Impl1));
+ assertFalse(unregComponents.contains(component0));
+
+ circularDependencies = graph.getCircularDependencies();
+ assertEquals(1, circularDependencies.size());
+ circularDependency = circularDependencies.get(0);
+
+ assertEquals(3, circularDependency.getComponents().size());
+ assertTrue(circularDependency.getComponents().contains(s6s7Impl));
+ assertTrue(circularDependency.getComponents().contains(s8Impl1));
+
+ missingDependencies = graph.getMissingDependencies("service");
+ assertEquals(1, missingDependencies.size());
+
+ missingDependencies = graph.getMissingDependencies("configuration");
+ assertEquals(1, missingDependencies.size());
+
+ // call getCircularDependencies again on the same graph
+ circularDependencies = graph.getCircularDependencies();
+ assertEquals(1, circularDependencies.size());
+ circularDependency = circularDependencies.get(0);
+
+ assertEquals(3, circularDependency.getComponents().size());
+ assertTrue(circularDependency.getComponents().contains(s6s7Impl));
+ assertTrue(circularDependency.getComponents().contains(s8Impl1));
+
+ List<MissingDependency> allMissingDependencies = graph.getMissingDependencies(null);
+ assertEquals(2, allMissingDependencies.size());
+
+ }
+
+ static interface S1 {}
+ static interface S2 {}
+ static interface S3 {}
+ static interface S4 {}
+ static interface S5 {}
+ static interface S6 {}
+ static interface S7 {}
+ static interface S8 {}
+ static class C0 {
+ public C0() {}
+ }
+ static class C1 {
+ public C1() {}
+ }
+ static class S1Impl1 implements S1 {
+ public S1Impl1(){}
+ }
+ static class S1Impl2 implements S1 {
+ public S1Impl2() {}
+ }
+ static class S1Impl3 implements S1 {
+ public S1Impl3() {}
+ }
+ static class S2Impl1 implements S2 {
+ public S2Impl1() {}
+ }
+ static class S3Impl1 implements S3 {
+ public S3Impl1() {}
+ }
+ static class S3S4Impl implements S3, S4 {
+ public S3S4Impl() {}
+ }
+ static class S4Impl1 implements S4 {
+ public S4Impl1() {}
+ }
+ static class S5Impl1 implements S5 {
+ public S5Impl1() {}
+ }
+ static class S6S7Impl implements S6, S7 {
+ public S6S7Impl() {}
+ }
+ static class S8Impl1 implements S8 {
+ public S8Impl1() {}
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.shell/bnd.bnd b/dependencymanager/org.apache.felix.dependencymanager.shell/bnd.bnd
index 08e99e2..1a649f0 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.shell/bnd.bnd
+++ b/dependencymanager/org.apache.felix.dependencymanager.shell/bnd.bnd
@@ -24,7 +24,7 @@
Private-Package: \
org.apache.felix.dm.shell
Bundle-Activator:org.apache.felix.dm.shell.Activator
-Bundle-Version: 4.0.1
+Bundle-Version: 4.0.2
Include-Resource: META-INF/=resources/LICENSE,\
META-INF/=resources/NOTICE,\
META-INF/=resources/DEPENDENCIES,\
diff --git a/dependencymanager/org.apache.felix.dependencymanager.shell/resources/changelog.txt b/dependencymanager/org.apache.felix.dependencymanager.shell/resources/changelog.txt
index 2e15e6d..51be153 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.shell/resources/changelog.txt
+++ b/dependencymanager/org.apache.felix.dependencymanager.shell/resources/changelog.txt
@@ -1,3 +1,9 @@
+Release org.apache.felix.dependencymanager.r3:
+---------------------------------------------
+
+** Improvement
+ * [FELIX-4889] - Refactor dm shell command to use the org.apache.dm.diagnostics api
+
Release org.apache.felix.dependencymanager.r1:
---------------------------------------------
diff --git a/dependencymanager/org.apache.felix.dependencymanager.shell/src/org/apache/felix/dm/shell/DMCommand.java b/dependencymanager/org.apache.felix.dependencymanager.shell/src/org/apache/felix/dm/shell/DMCommand.java
index e4171cb..440566c 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.shell/src/org/apache/felix/dm/shell/DMCommand.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.shell/src/org/apache/felix/dm/shell/DMCommand.java
@@ -23,19 +23,19 @@
import java.util.Comparator;
import java.util.Dictionary;
import java.util.Hashtable;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
import java.util.StringTokenizer;
-import java.util.TreeSet;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
import org.apache.felix.dm.ComponentDependencyDeclaration;
import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.diagnostics.CircularDependency;
+import org.apache.felix.dm.diagnostics.DependencyGraph;
+import org.apache.felix.dm.diagnostics.DependencyGraph.ComponentState;
+import org.apache.felix.dm.diagnostics.DependencyGraph.DependencyState;
+import org.apache.felix.dm.diagnostics.MissingDependency;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Descriptor;
import org.apache.felix.service.command.Parameter;
@@ -57,11 +57,11 @@
* Bundle context used to create OSGi filters.
*/
private final BundleContext m_context;
-
+
/**
- * Sorter used to sort components.
+ * Comparator used to compare component declarations based on their bundle ids
*/
- private static final DependencyManagerSorter SORTER = new DependencyManagerSorter();
+ private static final ComponentDeclarationComparator COMPONENT_DECLARATION_COMPARATOR = new ComponentDeclarationComparator();
/**
* Constant used by the wtf command, when listing missing services.
@@ -72,6 +72,16 @@
* Constant used by the wtf command, when listing missing configurations.
*/
private static final String CONFIGURATION = "configuration";
+
+ /**
+ * Constant used by the wtf command, when listing missing resource dependencies
+ */
+ private static final String RESOURCE = "resource";
+
+ /**
+ * Constant used by the wtf command, when listing missing bundle dependencies
+ */
+ private static final String BUNDLE = "bundle";
/**
* Name of a specific gogo shell variable, which may be used to configure "compact" mode.
@@ -212,100 +222,96 @@
wtf();
return;
}
-
- // lookup all dependency manager service components
- List<DependencyManager> managers = DependencyManager.getDependencyManagers();
- Collections.sort(managers, SORTER);
- Iterator<DependencyManager> iterator = managers.iterator();
+
+ DependencyGraph graph = null;
+ if(notavail) {
+ graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.ALL_UNAVAILABLE);
+ } else {
+ graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
+ }
+
+ List<ComponentDeclaration> allComponents = graph.getAllComponents();
+ Collections.sort(allComponents, COMPONENT_DECLARATION_COMPARATOR);
long numberOfComponents = 0;
long numberOfDependencies = 0;
long lastBundleId = -1;
- while (iterator.hasNext()) {
- DependencyManager manager = iterator.next();
- List<Component> complist = manager.getComponents();
- Iterator<Component> componentIterator = complist.iterator();
- while (componentIterator.hasNext()) {
- Component component = componentIterator.next();
- ComponentDeclaration sc = component.getComponentDeclaration();
- String name = sc.getName();
- // check if this component is enabled or disabled.
- if (!mayDisplay(component, servicesFilter, componentsRegex, cids)) {
- continue;
+
+ for(ComponentDeclaration cd : allComponents) {
+ Bundle bundle = cd.getBundleContext().getBundle();
+ if(!matchBundle(bundle, bids)) {
+ continue;
+ }
+
+ Component component = (Component)cd;
+ String name = cd.getName();
+ if (!mayDisplay(component, servicesFilter, componentsRegex, cids)) {
+ continue;
+ }
+
+ numberOfComponents++;
+ long bundleId = bundle.getBundleId();
+ if(lastBundleId != bundleId) {
+ lastBundleId = bundleId;
+ if (comp) {
+ System.out.println("[" + bundleId + "] " + compactName(bundle.getSymbolicName()));
+ } else {
+ System.out.println("[" + bundleId + "] " + bundle.getSymbolicName());
}
- int state = sc.getState();
- Bundle bundle = sc.getBundleContext().getBundle();
- if (matchBundle(bundle, bids)) {
- long bundleId = bundle.getBundleId();
- if (notavail) {
- if (sc.getState() != ComponentDeclaration.STATE_UNREGISTERED) {
- continue;
- }
+ }
+ if (comp) {
+ System.out.print(" [" + cd.getId() + "] " + compactName(name) + " "
+ + compactState(ComponentDeclaration.STATE_NAMES[cd.getState()]));
+ } else {
+ System.out.println(" [" + cd.getId() + "] " + name + " "
+ + ComponentDeclaration.STATE_NAMES[cd.getState()]);
+ }
+
+ if(!nodeps) {
+ List<ComponentDependencyDeclaration> dependencies = graph.getDependecies(cd);
+ if(!dependencies.isEmpty()) {
+ numberOfDependencies += dependencies.size();
+ if (comp) {
+ System.out.print('(');
}
+ for(int j = 0; j < dependencies.size(); j ++) {
+ ComponentDependencyDeclaration dep = dependencies.get(j);
+
+ String depName = dep.getName();
+ String depType = dep.getType();
+ int depState = dep.getState();
- numberOfComponents++;
- if (lastBundleId != bundleId) {
- lastBundleId = bundleId;
if (comp) {
- System.out.println("[" + bundleId + "] " + compactName(bundle.getSymbolicName()));
+ if (j > 0) {
+ System.out.print(' ');
+ }
+ System.out.print(compactName(depName) + " " + compactState(depType) + " "
+ + compactState(ComponentDependencyDeclaration.STATE_NAMES[depState]));
} else {
- System.out.println("[" + bundleId + "] " + bundle.getSymbolicName());
+ System.out.println(" " + depName + " " + depType + " "
+ + ComponentDependencyDeclaration.STATE_NAMES[depState]);
}
- }
- if (comp) {
- System.out.print(" [" + sc.getId() + "] " + compactName(name) + " "
- + compactState(ComponentDeclaration.STATE_NAMES[state]));
- } else {
- System.out.println(" [" + sc.getId() + "] " + name + " "
- + ComponentDeclaration.STATE_NAMES[state]);
- }
- if (!nodeps) {
- ComponentDependencyDeclaration[] dependencies = sc.getComponentDependencies();
- if (dependencies != null && dependencies.length > 0) {
- numberOfDependencies += dependencies.length;
- if (comp) {
- System.out.print('(');
- }
- for (int j = 0; j < dependencies.length; j++) {
- ComponentDependencyDeclaration dep = dependencies[j];
- if (notavail && !isUnavailable(dep)) {
- continue;
- }
- String depName = dep.getName();
- String depType = dep.getType();
- int depState = dep.getState();
- if (comp) {
- if (j > 0) {
- System.out.print(' ');
- }
- System.out.print(compactName(depName) + " " + compactState(depType) + " "
- + compactState(ComponentDependencyDeclaration.STATE_NAMES[depState]));
- } else {
- System.out.println(" " + depName + " " + depType + " "
- + ComponentDependencyDeclaration.STATE_NAMES[depState]);
- }
- }
- if (comp) {
- System.out.print(')');
- }
- }
- }
+ }
if (comp) {
- System.out.println();
+ System.out.print(')');
}
- }
+ }
+ }
+ if (comp) {
+ System.out.println();
}
}
-
- if (stats) {
- System.out.println("Statistics:");
- System.out.println(" - Dependency managers: " + managers.size());
- System.out.println(" - Components: " + numberOfComponents);
+
+ if(stats) {
+ System.out.println("Statistics:");
+ System.out.println(" - Dependency managers: " + DependencyManager.getDependencyManagers().size());
+ System.out.println(" - Components: " + numberOfComponents);
if (!nodeps) {
System.out.println(" - Dependencies: " + numberOfDependencies);
}
}
- }
+
+ }
/**
* Displays components callbacks (init/start/stop/destroy) elapsed time.
@@ -356,16 +362,6 @@
}
}
- private boolean isUnavailable(ComponentDependencyDeclaration dep) {
- switch (dep.getState()) {
- case ComponentDependencyDeclaration.STATE_UNAVAILABLE_OPTIONAL:
- case ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED:
- return true;
- default:
- return false;
- }
- }
-
private boolean matchBundle(Bundle bundle, List<String> ids) {
if (ids.size() == 0) {
return true;
@@ -553,46 +549,68 @@
}
public void wtf() {
- List<ComponentDeclaration> downComponents = getComponentsThatAreUnregistered();
- if (downComponents.isEmpty()) {
- System.out.println("No missing dependencies found.");
- }
- else {
- String message = downComponents.size() + " missing dependencies found.";
+
+ DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
+ List<ComponentDeclaration> unregisteredComponents = graph.getAllComponents();
+
+ if(unregisteredComponents.isEmpty()) {
+ System.out.println("No unregistered components found");
+ } else {
+ String message = unregisteredComponents.size() + " unregistered components found";
System.out.println(message);
System.out.println("----------------------------------------------------".substring(0, message.length()));
- }
- listResolvedBundles();
- listInstalledBundles();
- Set<ComponentId> downComponentsRoot = getTheRootCouses(downComponents);
- listAllMissingConfigurations(downComponentsRoot);
- listAllMissingServices(downComponents, downComponentsRoot);
+ }
+
+ listResolvedBundles();
+ listInstalledBundles();
+
+ List<CircularDependency> circularDependencies = graph.getCircularDependencies();
+ if(!circularDependencies.isEmpty()) {
+ System.out.println("Circular dependencies:");
+ printCircularDependencies(circularDependencies);
+ }
+
+ List<MissingDependency> missingConfigDependencies = graph.getMissingDependencies(CONFIGURATION);
+ if(!missingConfigDependencies.isEmpty()) {
+ System.out.println("The following configuration(s) are missing: ");
+ printMissingDependencies(missingConfigDependencies);
+ }
+
+ List<MissingDependency> missingServiceDependencies = graph.getMissingDependencies(SERVICE);
+ if(!missingServiceDependencies.isEmpty()) {
+ System.out.println("The following service(s) are missing: ");
+ printMissingDependencies(missingServiceDependencies);
+ }
+
+
+ List<MissingDependency> missingResourceDependencies = graph.getMissingDependencies(RESOURCE);
+ if(!missingResourceDependencies.isEmpty()) {
+ System.out.println("The following resource(s) are missing: ");
+ printMissingDependencies(missingResourceDependencies);
+ }
+
+ List<MissingDependency> missingBundleDependencies = graph.getMissingDependencies(BUNDLE);
+ if(!missingBundleDependencies.isEmpty()) {
+ System.out.println("The following bundle(s) are missing: ");
+ printMissingDependencies(missingBundleDependencies);
+ }
}
- private Set<ComponentId> getTheRootCouses(List<ComponentDeclaration> downComponents) {
- Set<ComponentId> downComponentsRoot = new TreeSet<ComponentId>();
- for (ComponentDeclaration c : downComponents) {
- List<ComponentId> root = getRoot(downComponents, c, new ArrayList<ComponentId>());
- downComponentsRoot.addAll(root);
- }
- return downComponentsRoot;
- }
+ private void printCircularDependencies(List<CircularDependency> circularDependencies) {
+ for(CircularDependency c : circularDependencies) {
+ System.out.print(" *");
+ for(ComponentDeclaration cd : c.getComponents()) {
+ System.out.print(" -> " + cd.getName());
+ }
+ System.out.println();
+ }
+ }
- private List<ComponentDeclaration> getComponentsThatAreUnregistered() {
- List<DependencyManager> dependencyManagers = DependencyManager.getDependencyManagers();
- List<ComponentDeclaration> unregisteredComponents = new ArrayList<ComponentDeclaration>();
- for (DependencyManager dm : dependencyManagers) {
- List<Component> components = dm.getComponents();
- // create a list of all components that are unregistered
- for (Component c : components) {
- ComponentDeclaration cd = c.getComponentDeclaration();
- if (cd.getState() == ComponentDeclaration.STATE_UNREGISTERED) {
- unregisteredComponents.add(cd);
- }
- }
- }
- return unregisteredComponents;
- }
+ private void printMissingDependencies(List<MissingDependency> missingConfigDependencies) {
+ for(MissingDependency m : missingConfigDependencies) {
+ System.out.println(" * " + m.getName() + " for bundle " + m.getBundleName());
+ }
+ }
private void listResolvedBundles() {
boolean areResolved = false;
@@ -635,150 +653,19 @@
Dictionary<String, String> headers = b.getHeaders();
return headers.get("Fragment-Host") != null;
}
-
- private void listAllMissingConfigurations(Set<ComponentId> unregisteredComponentsRoot) {
- if (hasMissingType(unregisteredComponentsRoot, CONFIGURATION)) {
- System.out.println("The following configuration(s) are missing: ");
- for (ComponentId s : unregisteredComponentsRoot) {
- if (CONFIGURATION.equals(s.getType())) {
- System.out.println(" * " + s.getName() + " for bundle " + s.getBundleName());
- }
- }
- }
- }
-
- private void listAllMissingServices(List<ComponentDeclaration> downComponents, Set<ComponentId> unregisteredComponentsRoot) {
- if (hasMissingType(unregisteredComponentsRoot, SERVICE)) {
- System.out.println("The following service(s) are missing: ");
- for (ComponentId s : unregisteredComponentsRoot) {
- if (SERVICE.equals(s.getType())) {
- System.out.print(" * " + s.getName());
- ComponentDeclaration component = getComponentDeclaration(s.getName(), downComponents);
- if (component == null) {
- System.out.println(" is not found in the service registry");
- } else {
- ComponentDependencyDeclaration[] componentDependencies = component.getComponentDependencies();
- System.out.println(" and needs:");
- for (ComponentDependencyDeclaration cdd : componentDependencies) {
- if (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED) {
- System.out.println(cdd.getName());
- }
- }
- System.out.println(" to work");
- }
- }
- }
- }
- }
-
- private boolean hasMissingType(Set<ComponentId> downComponentsRoot, String type) {
- for (ComponentId s : downComponentsRoot) {
- if (type.equals(s.getType())) {
- return true;
- }
- }
- return false;
- }
- private List<ComponentId> getRoot(List<ComponentDeclaration> downComponents, ComponentDeclaration c, List<ComponentId> backTrace) {
- ComponentDependencyDeclaration[] componentDependencies = c.getComponentDependencies();
- int unregisteredDeps = 0;
- List<ComponentId> result = new ArrayList<ComponentId>();
- for (ComponentDependencyDeclaration cdd : componentDependencies) {
- if (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED) {
- unregisteredDeps++;
- // Detect missing configuration dependency
- if (CONFIGURATION.equals(cdd.getType())) {
- String bsn = c.getBundleContext().getBundle().getSymbolicName();
- result.add(new ComponentId(cdd.getName(), cdd.getType(), bsn));
- continue;
- }
-
- // Detect if the missing dependency is a root cause failure
- ComponentDeclaration component = getComponentDeclaration(cdd.getName(), downComponents);
- if (component == null) {
- result.add(new ComponentId(cdd.getName(), cdd.getType(), null));
- continue;
- }
- // Detect circular dependency
- ComponentId componentId = new ComponentId(cdd.getName(), cdd.getType(), null);
- if (backTrace.contains(componentId)) {
- // We already got this one so it's a circular dependency
- System.out.print("Circular dependency found:\n *");
- for (ComponentId cid : backTrace) {
- System.out.print(" -> " + cid.getName() + " ");
- }
- System.out.println(" -> " + componentId.getName());
- result.add(new ComponentId(c.getName(), SERVICE, c.getBundleContext().getBundle().getSymbolicName()));
- continue;
- }
- backTrace.add(componentId);
- return getRoot(downComponents, component, backTrace);
- }
- }
- if (unregisteredDeps > 0 && result.isEmpty()) {
- result.add(new ComponentId(c.getName(), SERVICE, c.getBundleContext().getBundle().getSymbolicName()));
- }
- return result;
- }
-
- private ComponentDeclaration getComponentDeclaration(final String fullName, List<ComponentDeclaration> list) {
- String simpleName = getSimpleName(fullName);
- Properties props = parseProperties(fullName);
- for (ComponentDeclaration c : list) {
- String serviceNames = c.getName();
- int cuttOff = serviceNames.indexOf("(");
- if (cuttOff != -1) {
- serviceNames = serviceNames.substring(0, cuttOff).trim();
- }
- for (String serviceName : serviceNames.split(",")) {
- if (simpleName.equals(serviceName.trim()) && doPropertiesMatch(props, parseProperties(c.getName()))) {
- return c;
- }
- }
- }
- return null;
- }
-
- private boolean doPropertiesMatch(Properties need, Properties provide) {
- for (Entry<Object, Object> entry : need.entrySet()) {
- Object prop = provide.get(entry.getKey());
- if (prop == null || !prop.equals(entry.getValue())) {
- return false;
- }
- }
- return true;
- }
-
- private String getSimpleName(String name) {
- int cuttOff = name.indexOf("(");
- if (cuttOff != -1) {
- return name.substring(0, cuttOff).trim();
- }
- return name.trim();
- }
-
- private Properties parseProperties(String name) {
- Properties result = new Properties();
- int cuttOff = name.indexOf("(");
- if (cuttOff != -1) {
- String propsText = name.substring(cuttOff + 1, name.indexOf(")"));
- String[] split = propsText.split(",");
- for (String prop : split) {
- String[] kv = prop.split("=");
- if (kv.length == 2) {
- result.put(kv[0], kv[1]);
- }
- }
- }
- return result;
- }
-
- public static class DependencyManagerSorter implements Comparator<DependencyManager> {
- public int compare(DependencyManager dm1, DependencyManager dm2) {
- long id1 = dm1.getBundleContext().getBundle().getBundleId();
- long id2 = dm2.getBundleContext().getBundle().getBundleId();
- return id1 > id2 ? 1 : -1;
- }
+ public static class ComponentDeclarationComparator implements Comparator<ComponentDeclaration> {
+ @Override
+ public int compare(ComponentDeclaration cd1, ComponentDeclaration cd2) {
+ long id1 = cd1.getBundleContext().getBundle().getBundleId();
+ long id2 = cd2.getBundleContext().getBundle().getBundleId();
+ if(id1 == id2) {
+ // sort by component id
+ long cid1 = cd1.getId();
+ long cid2 = cd2.getId();
+ return cid1 > cid2 ? 1 : -1;
+ }
+ return id1 > id2 ? 1 : -1;
+ }
}
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.shell/test/test/DMCommandTest.java b/dependencymanager/org.apache.felix.dependencymanager.shell/test/test/DMCommandTest.java
index 18d0f0e..24ea66d 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.shell/test/test/DMCommandTest.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.shell/test/test/DMCommandTest.java
@@ -70,14 +70,12 @@
System.setErr(new PrintStream(errContent));
dm = new DependencyManager(m_bundleContext);
dme = new DMCommand(m_bundleContext);
- DependencyManager.getDependencyManagers().add(dm);
}
@After
public void cleanUp() {
System.setOut(null);
System.setErr(null);
- DependencyManager.getDependencyManagers().remove(dm);
}
@Test
@@ -86,7 +84,7 @@
setupEmptyBundles();
dme.wtf();
- assertEquals("No missing dependencies found.\n", outContent.toString());
+ assertEquals("No unregistered components found\n", outContent.toString());
}
@Test
@@ -99,7 +97,7 @@
.setInterface(Object.class.getName(), null)
);
dme.wtf();
- assertEquals("No missing dependencies found.\n", outContent.toString());
+ assertEquals("No unregistered components found\n", outContent.toString());
}
@Test
@@ -117,7 +115,7 @@
dme.wtf();
String output = outContent.toString();
- assertTrue(output.contains("1 missing"));
+ assertTrue(output.contains("1 unregistered"));
assertTrue(output.contains("java.lang.Math"));
// remove the mess
@@ -145,8 +143,8 @@
dme.wtf();
String output = outContent.toString();
- assertTrue(output.contains("Circular dependency found:"));
- assertTrue(output.contains("-> java.lang.Math -> javax.crypto.Cipher -> java.lang.Math"));
+ assertTrue(output.contains("-> java.lang.Math -> javax.crypto.Cipher -> java.lang.Math") ||
+ output.contains("-> javax.crypto.Cipher -> java.lang.Math -> javax.crypto.Cipher"));
// remove the mess
dm.remove(component1);
@@ -172,7 +170,7 @@
dme.wtf();
String output = outContent.toString();
- assertTrue(output.contains("2 missing"));
+ assertTrue(output.contains("2 unregistered"));
assertTrue(output.contains("java.lang.String"));
// remove the mess
@@ -205,7 +203,7 @@
dme.wtf();
String output = outContent.toString();
- assertTrue(output.contains("3 missing"));
+ assertTrue(output.contains("3 unregistered"));
assertTrue(output.contains("java.lang.String"));
assertFalse(output.contains("java.lang.Float"));
@@ -230,7 +228,7 @@
dme.wtf();
String output = outContent.toString();
- assertTrue(output.contains("1 missing"));
+ assertTrue(output.contains("1 unregistered"));
assertTrue(output.contains("java.lang.Math"));
assertTrue(output.contains("java.lang.Long"));
diff --git a/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd b/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
index b42cd3c..c461405 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
+++ b/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
@@ -24,10 +24,11 @@
org.apache.felix.dm.impl.index,\
org.apache.felix.dm.impl.index.multiproperty,\
org.apache.felix.dm.impl.metatype
-Export-Package: \
+Export-Package: \
org.apache.felix.dm,\
org.apache.felix.dm.tracker,\
- org.apache.felix.dm.context
+ org.apache.felix.dm.context,\
+ org.apache.felix.dm.diagnostics
Include-Resource: META-INF/=resources/LICENSE,\
META-INF/=resources/NOTICE,\
META-INF/=resources/DEPENDENCIES,\
diff --git a/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt b/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt
index b96214a..98e76f2 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt
+++ b/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt
@@ -10,7 +10,8 @@
* [FELIX-4878] - Support more signatures for Dependency callbacks
* [FELIX-4879] - ConfigurationDependency should always "need instance".
* [FELIX-4880] - Missing callback instance support for some adapters
-
+ * [FELIX-4873] - Enhance DM API to get missing and circular dependencies
+
** Wish
* [FELIX-4875] - Update DM integration test with latest ConfigAdmin
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/CircularDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/CircularDependency.java
new file mode 100644
index 0000000..8d4de08
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/CircularDependency.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dm.diagnostics;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.dm.ComponentDeclaration;
+
+public class CircularDependency {
+
+ private List<ComponentDeclaration> m_components = new ArrayList<>();
+
+ void addComponent(ComponentDeclaration component) {
+ m_components.add(component);
+ }
+
+ public List<ComponentDeclaration> getComponents() {
+ return Collections.unmodifiableList(m_components);
+ }
+
+ @Override
+ public String toString() {
+ String result = "";
+ for(ComponentDeclaration c : m_components) {
+ result += " -> " + c.getName();
+ }
+ return result;
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/ComponentNode.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/ComponentNode.java
new file mode 100644
index 0000000..8d8fcdb
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/ComponentNode.java
@@ -0,0 +1,40 @@
+/*
+ * 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.dm.diagnostics;
+
+import org.apache.felix.dm.ComponentDeclaration;
+
+class ComponentNode extends DependencyGraphNode {
+
+ private ComponentDeclaration m_componentDeclaration;
+
+ public ComponentNode(ComponentDeclaration componentDeclaration) {
+ m_componentDeclaration = componentDeclaration;
+ }
+
+ public ComponentDeclaration getComponentDeclaration() {
+ return m_componentDeclaration;
+ }
+
+ @Override
+ public String toString() {
+ return m_componentDeclaration.getName();
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraph.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraph.java
new file mode 100644
index 0000000..5822242
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraph.java
@@ -0,0 +1,392 @@
+/*
+ * 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.dm.diagnostics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Stack;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.Bundle;
+
+/**
+ * The dependency graph is a view of all components managed by the dependency manager
+ * and of their dependencies. Using this API you can get the dependencies of a given component,
+ * the components providing a given service, the circular dependencies that might exist.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ *
+ */
+public class DependencyGraph {
+
+ /**
+ * Use this to specify which components the dependency graph should contain
+ */
+ public enum ComponentState {
+ ALL,
+ UNREGISTERED
+ };
+
+ /**
+ * Use this to specify which dependencies the graph should contain
+ */
+ public enum DependencyState {
+ ALL,
+ ALL_UNAVAILABLE,
+ REQUIRED_UNAVAILABLE
+ };
+
+ private static final String SERVICE = "service";
+
+ private Map<ComponentDeclaration, DependencyGraphNode> m_componentToNode = new HashMap<>();
+ private Map<ComponentDependencyDeclaration, DependencyGraphNode> m_dependencyToNode = new HashMap<>();
+ private List<List<DependencyGraphNode>> m_circularDependencies = new ArrayList<>();
+ private Map<DependencyGraphNode, DependencyGraphNode> m_parent = new HashMap<>();
+
+ private ComponentState m_componentState = ComponentState.ALL;
+ private DependencyState m_dependencyState = DependencyState.ALL;
+
+ private DependencyGraph(ComponentState componentState, DependencyState dependencyState) {
+
+ m_componentState = componentState;
+ m_dependencyState = dependencyState;
+
+ buildComponentNodes();
+ buildDependecyNodesAndEdges();
+
+ }
+
+ private void buildComponentNodes() {
+ List<DependencyManager> dependencyManagers = DependencyManager.getDependencyManagers();
+ for(DependencyManager dm : dependencyManagers) {
+ List<Component> components = dm.getComponents();
+ for(Component c : components) {
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ if(componentMustBeAddedToGraph(cd)) {
+ m_componentToNode.put(cd, new ComponentNode(cd));
+ }
+ }
+ }
+ }
+
+ private boolean componentMustBeAddedToGraph(ComponentDeclaration cd) {
+ if(m_componentState == ComponentState.ALL) {
+ return true;
+ } else if(m_componentState == ComponentState.UNREGISTERED) {
+ return cd.getState() == ComponentDeclaration.STATE_UNREGISTERED;
+ }
+ return false;
+ }
+
+ private void buildDependecyNodesAndEdges() {
+
+ for(DependencyGraphNode node : m_componentToNode.values()) {
+ ComponentNode componentNode = (ComponentNode)node;
+ ComponentDependencyDeclaration[] dependencyDeclarations = componentNode.getComponentDeclaration().getComponentDependencies();
+
+ for(ComponentDependencyDeclaration cdd : dependencyDeclarations) {
+ if(dependencyMustBeAddedToGraph(cdd)) {
+ DependencyNode dependencyNode = new DependencyNode(cdd);
+ m_dependencyToNode.put(cdd, dependencyNode);
+
+ // add edges from the component node to newly created dependency node
+ componentNode.addSuccessor(dependencyNode);
+
+ // add edges from the newly created dependency node to the components
+ // providing those dependencies (only applicable to service dependencies)
+ List<ComponentNode> providerComponents = getProviderComponents(dependencyNode);
+ for(ComponentNode p : providerComponents) {
+ dependencyNode.addSuccessor(p);
+ }
+ }
+ }
+ }
+ }
+
+ private List<ComponentNode> getProviderComponents(DependencyNode dependencyNode) {
+ List<ComponentNode> result = new ArrayList<>();
+
+ ComponentDependencyDeclaration cdd = dependencyNode.getDependencyDeclaration();
+ if(!SERVICE.equals(cdd.getType())) {
+ return result;
+ }
+
+ for(DependencyGraphNode n : m_componentToNode.values()) {
+ ComponentNode componentNode = (ComponentNode)n;
+ if(componentProvidesDependency(componentNode, dependencyNode)) {
+ result.add(componentNode);
+ }
+ }
+
+ return result;
+ }
+
+ private boolean componentProvidesDependency(ComponentNode componentNode, DependencyNode dependencyNode) {
+ ComponentDeclaration cd = componentNode.getComponentDeclaration();
+
+ String dependencyName = dependencyNode.getDependencyDeclaration().getName();
+ String simpleName = getSimpleName(dependencyName);
+ Properties properties = parseProperties(dependencyName);
+
+ String componentName = cd.getName();
+ int cuttOff = componentName.indexOf("(");
+ if (cuttOff != -1) {
+ componentName = componentName.substring(0, cuttOff).trim();
+ }
+ for (String serviceName : componentName.split(",")) {
+ if (simpleName.equals(serviceName.trim()) && doPropertiesMatch(properties, parseProperties(cd.getName()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean dependencyMustBeAddedToGraph(ComponentDependencyDeclaration cdd) {
+ if(m_dependencyState == DependencyState.ALL) {
+ return true;
+ } else if(m_dependencyState == DependencyState.ALL_UNAVAILABLE) {
+ return
+ (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED) ||
+ (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_OPTIONAL);
+ } else if(m_dependencyState == DependencyState.REQUIRED_UNAVAILABLE) {
+ return cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED;
+
+ }
+ return false;
+ }
+
+ /**
+ * Build the dependency graph. It will contain all the components managed by the dependency manager, provided
+ * that those components are in the given state, and all dependencies of their dependencies, provided that the
+ * dependencies are in the given state.
+ *
+ * <p>This implementation currently only builds a graph of unregistered components and
+ * required unavailable dependencies.
+ *
+ * @param componentState Include only the components in this state
+ * @param dependencyState Include only the dependencies in this state
+ * @return
+ */
+ public static DependencyGraph getGraph(ComponentState componentState, DependencyState dependencyState) {
+ return new DependencyGraph(componentState, dependencyState);
+ }
+
+ /**
+ * Returns the list of components in the graph
+ * @return the list of components in the graph
+ */
+ public List<ComponentDeclaration> getAllComponents() {
+ return new ArrayList<ComponentDeclaration>(m_componentToNode.keySet());
+ }
+
+ /**
+ * Returns a list all dependencies in the graph
+ * @return the list of all dependencies in the graph
+ */
+ public List<ComponentDependencyDeclaration> getAllDependencies() {
+ return new ArrayList<ComponentDependencyDeclaration>(m_dependencyToNode.keySet());
+
+ }
+
+ /**
+ * For a given component declaration, it returns a list of its dependencies in the state
+ * specified when the graph was built.
+ * @param componentDeclaration
+ * @return the list of dependencies or null if the component declaration is not in the graph
+ */
+ public List<ComponentDependencyDeclaration> getDependecies(ComponentDeclaration componentDeclaration) {
+ List<ComponentDependencyDeclaration> result = new ArrayList<>();
+
+ DependencyGraphNode node = m_componentToNode.get(componentDeclaration);
+ if(node == null) {
+ return null;
+ }
+
+ for(DependencyGraphNode s : node.getSuccessors()) {
+ result.add( ((DependencyNode)s).getDependencyDeclaration() );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the list of components that provide the given dependency. This only returns the components
+ * managed by the dependency manager that are in the state specified when the graph was built. The
+ * dependency can only be a service dependency.
+ *
+ * @param dependency
+ * @return the list of components providing this dependency or null if the dependency declaration is
+ * not in the graph
+ */
+ public List<ComponentDeclaration> getProviders(ComponentDependencyDeclaration dependency) {
+ List<ComponentDeclaration> result = new ArrayList<>();
+
+ DependencyGraphNode node = m_dependencyToNode.get(dependency);
+ if(node == null) {
+ return null;
+ }
+
+ for(DependencyGraphNode s : node.getSuccessors()) {
+ result.add(((ComponentNode)s).getComponentDeclaration());
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the list of circular dependencies in the graph
+ * @return the list of circular dependencies
+ */
+ public List<CircularDependency> getCircularDependencies() {
+ List<CircularDependency> result = new ArrayList<CircularDependency>();
+
+ for(DependencyGraphNode n : m_componentToNode.values()) {
+ if(n.isUndiscovered()) {
+ depthFirstSearch(n);
+ }
+ }
+
+ for(List<DependencyGraphNode> cycle : m_circularDependencies) {
+ CircularDependency circularDependency = new CircularDependency();
+ for(DependencyGraphNode n : cycle) {
+ if(n instanceof ComponentNode) {
+ circularDependency.addComponent(((ComponentNode) n).getComponentDeclaration());
+ }
+ }
+
+ result.add(circularDependency);
+ }
+
+ return result;
+ }
+
+ private void depthFirstSearch(DependencyGraphNode n) {
+
+ n.setState(DependencyGraphNode.DependencyGraphNodeState.DISCOVERED);
+ for(DependencyGraphNode s : n.getSuccessors()) {
+ if(s.isUndiscovered()) {
+ m_parent.put(s, n);
+ depthFirstSearch(s);
+ } else if(s.isDiscovered()) {
+ addCycle(n, s);
+ }
+ }
+ n.setState(DependencyGraphNode.DependencyGraphNodeState.PROCESSED);
+ }
+
+ private void addCycle(DependencyGraphNode n, DependencyGraphNode s) {
+ List<DependencyGraphNode> cycle = new ArrayList<>();
+ Stack<DependencyGraphNode> stack = new Stack<>();
+
+ stack.push(s);
+ for(DependencyGraphNode p = n; p != s; p = m_parent.get(p)) {
+ stack.push(p);
+ }
+ stack.push(s);
+
+ while(!stack.isEmpty()) {
+ cycle.add(stack.pop());
+ }
+ m_circularDependencies.add(cycle);
+ }
+
+ /**
+ * Returns all the missing dependencies of a given type.
+ * @param type The type of the dependencies to be returned. This can be either one of the types
+ * known by the DependencyManager (service, bundle, configuration, resource),
+ * a user defined type or null, in which case all missing dependencies must be returned.
+ *
+ * @return The missing dependencies of the given type or all the missing dependencies.
+ */
+ public List<MissingDependency> getMissingDependencies(String type) {
+
+ List<MissingDependency> result = new ArrayList<>();
+
+ // get all dependency nodes that have no out-going edges
+ List<DependencyNode> missingDependencies = new ArrayList<>();
+ for(DependencyGraphNode node : m_dependencyToNode.values()) {
+ DependencyNode dependencyNode = (DependencyNode)node;
+ if(!dependencyNode.isUnavailable()) {
+ continue;
+ }
+
+ if( (type != null) && (!dependencyNode.getDependencyDeclaration().getType().equals(type)) ) {
+ continue;
+ }
+ if (dependencyNode.getSuccessors().isEmpty()) {
+ missingDependencies.add(dependencyNode);
+ }
+ }
+
+ for(DependencyNode node : missingDependencies) {
+ for(DependencyGraphNode p : node.getPredecessors()) {
+ ComponentNode componentNode = (ComponentNode)p;
+ Bundle bundle = componentNode.getComponentDeclaration().getBundleContext().getBundle();
+ MissingDependency missingDependency = new MissingDependency(
+ node.getDependencyDeclaration().getName(),
+ node.getDependencyDeclaration().getType(),
+ bundle.getSymbolicName());
+ result.add(missingDependency);
+ }
+ }
+ return result;
+ }
+
+ private String getSimpleName(String name) {
+ int cuttOff = name.indexOf("(");
+ if (cuttOff != -1) {
+ return name.substring(0, cuttOff).trim();
+ }
+ return name.trim();
+ }
+
+ private Properties parseProperties(String name) {
+ Properties result = new Properties();
+ int cuttOff = name.indexOf("(");
+ if (cuttOff != -1) {
+ String propsText = name.substring(cuttOff + 1, name.indexOf(")"));
+ String[] split = propsText.split(",");
+ for (String prop : split) {
+ String[] kv = prop.split("=");
+ if (kv.length == 2) {
+ result.put(kv[0], kv[1]);
+ }
+ }
+ }
+ return result;
+ }
+
+ private boolean doPropertiesMatch(Properties need, Properties provide) {
+ for (Entry<Object, Object> entry : need.entrySet()) {
+ Object prop = provide.get(entry.getKey());
+ if (prop == null || !prop.equals(entry.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraphNode.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraphNode.java
new file mode 100644
index 0000000..775cd28
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyGraphNode.java
@@ -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.dm.diagnostics;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class DependencyGraphNode {
+
+ public enum DependencyGraphNodeState {
+ UNDISCOVERED,
+ DISCOVERED,
+ PROCESSED
+ };
+
+ private List<DependencyGraphNode> m_successors = new ArrayList<>();
+ private List<DependencyGraphNode> m_predecessors = new ArrayList<>();
+ private DependencyGraphNodeState m_state = DependencyGraphNodeState.UNDISCOVERED;
+
+ public void addSuccessor(DependencyGraphNode successor) {
+ m_successors.add(successor);
+ successor.addPredecessor(this);
+ }
+
+ private void addPredecessor(DependencyGraphNode predecessor) {
+ m_predecessors.add(predecessor);
+ }
+
+ public List<DependencyGraphNode> getSuccessors() {
+ return Collections.unmodifiableList(m_successors);
+ }
+
+ public List<DependencyGraphNode> getPredecessors() {
+ return Collections.unmodifiableList(m_predecessors);
+ }
+
+ void setState(DependencyGraphNodeState state) {
+ m_state = state;
+ }
+
+ boolean isDiscovered() {
+ return m_state == DependencyGraphNodeState.DISCOVERED;
+ }
+
+ boolean isUndiscovered() {
+ return m_state == DependencyGraphNodeState.UNDISCOVERED;
+ }
+
+ boolean isProcessed() {
+ return m_state == DependencyGraphNodeState.PROCESSED;
+ }
+
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyNode.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyNode.java
new file mode 100644
index 0000000..5eab1d2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/DependencyNode.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dm.diagnostics;
+
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+
+class DependencyNode extends DependencyGraphNode {
+
+ private ComponentDependencyDeclaration m_dependencyDeclaration;
+
+ public DependencyNode(ComponentDependencyDeclaration dependencyDeclaration) {
+ m_dependencyDeclaration = dependencyDeclaration;
+ }
+
+ public ComponentDependencyDeclaration getDependencyDeclaration() {
+ return m_dependencyDeclaration;
+ }
+
+ public boolean isUnavailableRequired() {
+ return m_dependencyDeclaration.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED;
+ }
+
+ public boolean isUnavailableOptional() {
+ return m_dependencyDeclaration.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_OPTIONAL;
+ }
+
+ public boolean isUnavailable() {
+ return m_dependencyDeclaration.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_OPTIONAL
+ || m_dependencyDeclaration.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED;
+ }
+
+ @Override
+ public String toString() {
+ return m_dependencyDeclaration.getName();
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/MissingDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/MissingDependency.java
new file mode 100644
index 0000000..3d872e4
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/MissingDependency.java
@@ -0,0 +1,62 @@
+/*
+ * 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.dm.diagnostics;
+
+/**
+ * This represents a missing dependency. It can have any of the four types known to the Dependency Manager (service,
+ * configuration, bundle and resource) or it can be a type defined by the programmer.
+ * A missing dependency is defined by its name, its type and the bundle name of the bundle for which
+ * this dependency is unavailable.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ *
+ */
+public class MissingDependency {
+
+ private final String name;
+ private final String type;
+ private final String bundleName;
+
+ public MissingDependency(String name, String type, String bundleName) {
+ this.name = name;
+ this.type = type;
+ this.bundleName = bundleName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getBundleName() {
+ return bundleName;
+ }
+
+ @Override
+ public String toString() {
+ return "Missing dependency: "
+ + "name = " + name + " "
+ + "type = " + type + " "
+ + "bundleName = " + bundleName;
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/packageinfo b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/packageinfo
new file mode 100644
index 0000000..e252556
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/diagnostics/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
\ No newline at end of file