You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2021/08/14 13:46:59 UTC

[felix-dev] branch master updated: FELIX-6445 : Recursive reference detection throws excepteion

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 286d7da  FELIX-6445 : Recursive reference detection throws excepteion
286d7da is described below

commit 286d7dae45e132369fd19e5bb1459dabadd80c13
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sat Aug 14 15:45:57 2021 +0200

    FELIX-6445 : Recursive reference detection throws excepteion
---
 rootcause/bnd.bnd                                  |   0
 rootcause/pom.xml                                  |  30 +++--
 .../java/org/apache/felix/rootcause/DSComp.java    |   6 +
 .../java/org/apache/felix/rootcause/DSRef.java     |   7 ++
 .../org/apache/felix/rootcause/DSRootCause.java    | 122 ++++++++++++++++-----
 .../apache/felix/rootcause/DSRootCauseTest.java    |  29 +++--
 .../org/apache/felix/rootcause/util/BaseTest.java  |  10 +-
 7 files changed, 149 insertions(+), 55 deletions(-)

diff --git a/rootcause/bnd.bnd b/rootcause/bnd.bnd
deleted file mode 100644
index e69de29..0000000
diff --git a/rootcause/pom.xml b/rootcause/pom.xml
index cfb7636..96de07e 100644
--- a/rootcause/pom.xml
+++ b/rootcause/pom.xml
@@ -15,7 +15,7 @@
     <parent>
         <groupId>org.apache.felix</groupId>
         <artifactId>felix-parent</artifactId>
-        <version>5</version>
+        <version>7</version>
         <relativePath />
     </parent>
 
@@ -40,7 +40,7 @@
             <plugin>
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
-                <version>4.0.0</version>
+                <version>5.3.0</version>
                 <executions>
                     <execution>
                         <goals>
@@ -75,20 +75,32 @@
     <dependencies>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>osgi.cmpn</artifactId>
-            <version>6.0.0</version>
+            <artifactId>osgi.core</artifactId>
+            <version>7.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
-            <version>6.0.0</version>
+            <artifactId>org.osgi.service.component</artifactId>
+            <version>1.4.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>osgi.annotation</artifactId>
-            <version>7.0.0</version>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+            <version>1.1.1</version>
+            <scope>provided</scope>
+        </dependency>
+         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.bundle</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -150,7 +162,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.6.10</version>
+            <version>6.0.5</version>
             <scope>test</scope>
         </dependency>
 
diff --git a/rootcause/src/main/java/org/apache/felix/rootcause/DSComp.java b/rootcause/src/main/java/org/apache/felix/rootcause/DSComp.java
index d232fa0..c3253ff 100644
--- a/rootcause/src/main/java/org/apache/felix/rootcause/DSComp.java
+++ b/rootcause/src/main/java/org/apache/felix/rootcause/DSComp.java
@@ -25,8 +25,14 @@ import org.osgi.dto.DTO;
 import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
 import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
 
+/**
+ * DTO for a component
+ */
 public class DSComp extends DTO {
+    /** The component description */
     public ComponentDescriptionDTO desc;
+    /** The component configuration */
     public ComponentConfigurationDTO config;
+    /** List of unsatisfied references. */
     public List<DSRef> unsatisfied = new ArrayList<>();
 }
diff --git a/rootcause/src/main/java/org/apache/felix/rootcause/DSRef.java b/rootcause/src/main/java/org/apache/felix/rootcause/DSRef.java
index 0989e70..b443d7a 100644
--- a/rootcause/src/main/java/org/apache/felix/rootcause/DSRef.java
+++ b/rootcause/src/main/java/org/apache/felix/rootcause/DSRef.java
@@ -23,9 +23,16 @@ import java.util.List;
 
 import org.osgi.dto.DTO;
 
+/**
+ * DTO for a reference
+ */
 public class DSRef extends DTO {
+    /** Reference name */
     public String name;
+    /** Referenced interface */
     public String iface;
+    /** Optional filter */
     public String filter;
+    /** List of candidates - might be empty. */
     public List<DSComp> candidates = new ArrayList<>();
 }
diff --git a/rootcause/src/main/java/org/apache/felix/rootcause/DSRootCause.java b/rootcause/src/main/java/org/apache/felix/rootcause/DSRootCause.java
index 5fac3f1..cee6be9 100644
--- a/rootcause/src/main/java/org/apache/felix/rootcause/DSRootCause.java
+++ b/rootcause/src/main/java/org/apache/felix/rootcause/DSRootCause.java
@@ -18,10 +18,13 @@
  */
 package org.apache.felix.rootcause;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.osgi.service.component.runtime.ServiceComponentRuntime;
@@ -30,67 +33,132 @@ import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
 import org.osgi.service.component.runtime.dto.ReferenceDTO;
 import org.osgi.service.component.runtime.dto.UnsatisfiedReferenceDTO;
 
+/**
+ * Utility class to get the root cause for declarative services.
+ */
 public class DSRootCause {
 
     private static final int MAX_RECURSION = 10;
-    
-    private ServiceComponentRuntime scr;
-    
-    public DSRootCause(ServiceComponentRuntime scr) {
+
+    private final ServiceComponentRuntime scr;
+
+    /**
+     * Create new instance
+     * @param scr Service component runtime
+     */
+    public DSRootCause(final ServiceComponentRuntime scr) {
         this.scr = scr;
     }
-    
-    public Optional<DSComp> getRootCause(String iface) {
-        return scr.getComponentDescriptionDTOs().stream()
+
+    /**
+     * Get the root cause based on an interface name
+     * @param iface The interface name
+     * @return Optional root cause
+     */
+    public Optional<DSComp> getRootCause(final String iface) {
+        return this.getRootCause(iface, null);
+    }
+
+    /**
+     * Get the root cause based on an interface name
+     * @param iface The interface name
+     * @param allDTOs A collection with all dtos as a cache to lookup, optional
+     * @return Optional root cause
+     * @since 0.2.0
+     */
+    public Optional<DSComp> getRootCause(final String iface, final Collection<ComponentDescriptionDTO> allDTOs) {
+        final Collection<ComponentDescriptionDTO> cache = allDTOs == null ? scr.getComponentDescriptionDTOs() : allDTOs;
+        return cache.stream()
             .filter(desc -> offersInterface(desc, iface))
-            .map(this::getRootCause)
+            .map(d -> getRootCause(d, cache))
             .findFirst();
     }
-    
-    public DSComp getRootCause(ComponentDescriptionDTO desc) {
-        return getRootCause(desc, 0);
+
+    /**
+     * Get the root cause for a component description
+     * @param desc The description
+     * @return The root cause
+     */
+    public DSComp getRootCause(final ComponentDescriptionDTO desc) {
+        return getRootCause(desc, null);
+    }
+
+    /**
+     * Get the root cause for a component description
+     * @param desc The description
+     * @param allDTOs A collection with all dtos as a cache to lookup, optional
+     * @return The root cause
+     * @since 0.2.0
+     */
+    public DSComp getRootCause(final ComponentDescriptionDTO desc, final Collection<ComponentDescriptionDTO> allDTOs) {
+        final Set<String> visitedNames = new HashSet<>();
+        return getRootCause(desc, visitedNames, 0, allDTOs == null ? new ArrayList<>() : allDTOs);
     }
 
-    private DSComp getRootCause(ComponentDescriptionDTO desc, int level) {
-        if (level > MAX_RECURSION) {
-            throw new IllegalStateException("Aborting after because of cyclic references");
+    private DSComp getRootCause(final ComponentDescriptionDTO desc,
+            final Set<String> visitedNames,
+            final int level,
+            final Collection<ComponentDescriptionDTO> cache) {
+        if (level > MAX_RECURSION || visitedNames.contains(desc.name)) {
+            return null;
         }
-        DSComp dsComp = new DSComp();
+        final boolean added = visitedNames.add(desc.name);
+        final DSComp dsComp = new DSComp();
         dsComp.desc = desc;
-        Collection<ComponentConfigurationDTO> instances = scr.getComponentConfigurationDTOs(desc);
+
+        final Collection<ComponentConfigurationDTO> instances = scr.getComponentConfigurationDTOs(desc);
         if (instances.isEmpty()) {
             return dsComp;
         }
-        for (ComponentConfigurationDTO instance : instances) {
-            for (UnsatisfiedReferenceDTO ref : instance.unsatisfiedReferences) {
-                ReferenceDTO refdef = getReference(desc, ref.name);
-                DSRef unresolvedRef = createRef(ref, refdef);
-                unresolvedRef.candidates = getCandidates(ref, refdef, level + 1);
+        for (final ComponentConfigurationDTO instance : instances) {
+            for (final UnsatisfiedReferenceDTO ref : instance.unsatisfiedReferences) {
+                final ReferenceDTO refdef = getReference(desc, ref.name);
+
+                final DSRef unresolvedRef = createRef(ref, refdef);
+                unresolvedRef.candidates = getCandidates(ref, refdef, visitedNames, level + 1, cache);
                 dsComp.unsatisfied.add(unresolvedRef);
             }
         }
+        if ( added ) {
+            visitedNames.remove(desc.name);
+        }
         return dsComp;
     }
 
     private DSRef createRef(UnsatisfiedReferenceDTO unsatifiedRef, ReferenceDTO refdef) {
-        DSRef ref = new DSRef();
+        final DSRef ref = new DSRef();
         ref.name = unsatifiedRef.name;
         ref.filter = unsatifiedRef.target;
         ref.iface = refdef.interfaceName;
         return ref;
     }
 
-    private List<DSComp> getCandidates(UnsatisfiedReferenceDTO ref, ReferenceDTO refdef, int level) {
-        return scr.getComponentDescriptionDTOs().stream()
-                .filter(desc -> offersInterface(desc, refdef.interfaceName))
-                .map(desc -> getRootCause(desc, level)).collect(Collectors.toList());
+    private List<DSComp> getCandidates(final UnsatisfiedReferenceDTO ref,
+            final ReferenceDTO refdef,
+            final Set<String> visitedNames,
+            final int level,
+            final Collection<ComponentDescriptionDTO> cache) {
+        if ( cache.isEmpty() ) {
+            cache.addAll(scr.getComponentDescriptionDTOs());
+        }
+        final List<DSComp> result = new ArrayList<>();
+
+        final List<ComponentDescriptionDTO> candidates = cache.stream()
+                .filter(desc -> offersInterface(desc, refdef.interfaceName)).collect(Collectors.toList());
+        for(final ComponentDescriptionDTO c : candidates) {
+            final DSComp r = getRootCause(c, visitedNames, level, cache);
+            if ( r != null ) {
+                result.add(r);
+            }
+        }
+        return result;
     }
 
     private boolean offersInterface(ComponentDescriptionDTO desc, String interfaceName) {
         return Arrays.asList(desc.serviceInterfaces).contains(interfaceName);
     }
 
-    private ReferenceDTO getReference(ComponentDescriptionDTO desc, String name) {
+    private ReferenceDTO getReference(final ComponentDescriptionDTO desc, final String name) {
         return Arrays.asList(desc.references).stream().filter(ref -> ref.name.equals(name)).findFirst().get();
     }
 
diff --git a/rootcause/src/test/java/org/apache/felix/rootcause/DSRootCauseTest.java b/rootcause/src/test/java/org/apache/felix/rootcause/DSRootCauseTest.java
index 4f2b24a..61804e2 100644
--- a/rootcause/src/test/java/org/apache/felix/rootcause/DSRootCauseTest.java
+++ b/rootcause/src/test/java/org/apache/felix/rootcause/DSRootCauseTest.java
@@ -22,14 +22,11 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.ops4j.pax.tinybundles.core.TinyBundles.bundle;
 
 import javax.inject.Inject;
 
-import org.apache.felix.rootcause.DSComp;
-import org.apache.felix.rootcause.DSRef;
-import org.apache.felix.rootcause.DSRootCause;
-import org.apache.felix.rootcause.RootCausePrinter;
 import org.apache.felix.rootcause.examples.CompWithCyclicRef;
 import org.apache.felix.rootcause.examples.CompWithMissingConfig;
 import org.apache.felix.rootcause.examples.CompWithMissingRef;
@@ -54,9 +51,9 @@ public class DSRootCauseTest extends BaseTest {
 
     @Inject
     ServiceComponentRuntime scr;
-    
+
     DSRootCause dsRootCause;
-    
+
     @Configuration
     public Option[] configuration() {
         return new Option[] {
@@ -70,13 +67,13 @@ public class DSRootCauseTest extends BaseTest {
                         )
         };
     }
-    
+
     @Before
     public void before() {
         dsRootCause = new DSRootCause(scr);
     }
 
-    
+
     @Test
     public void testMissingConfig() throws InterruptedException {
         ComponentDescriptionDTO desc = getComponentDesc(CompWithMissingConfig.class);
@@ -85,7 +82,7 @@ public class DSRootCauseTest extends BaseTest {
         assertEquals("CompWithMissingConfig", rootCause.desc.name);
         assertNull(rootCause.config);
     }
-    
+
     @Test
     public void testMissingRef() throws InterruptedException {
         ComponentDescriptionDTO desc = getComponentDesc(CompWithMissingRef.class);
@@ -98,7 +95,7 @@ public class DSRootCauseTest extends BaseTest {
         assertEquals("CompWithMissingConfig", candidate.desc.name);
         assertNull(candidate.config);
     }
-    
+
     @Test
     public void testMissingRef2() throws InterruptedException {
         ComponentDescriptionDTO desc = getComponentDesc(CompWithMissingRef2.class);
@@ -111,19 +108,21 @@ public class DSRootCauseTest extends BaseTest {
         assertEquals("CompWithMissingRef", candidate.desc.name);
         assertNull(candidate.config);
     }
-    
+
     @Test
     public void testNoService() throws InterruptedException {
         ComponentDescriptionDTO desc = getComponentDesc(CompWithoutService.class);
         DSComp rootCause = dsRootCause.getRootCause(desc);
-        assertThat(rootCause.desc.serviceInterfaces.length, equalTo(0)); 
+        assertThat(rootCause.desc.serviceInterfaces.length, equalTo(0));
         new RootCausePrinter().print(rootCause);
     }
-    
-    @Test(expected = IllegalStateException.class)
+
+    @Test
     public void testCyclic() throws InterruptedException {
         ComponentDescriptionDTO desc = getComponentDesc(CompWithCyclicRef.class);
-        dsRootCause.getRootCause(desc);
+        DSComp rootCause = dsRootCause.getRootCause(desc);
+        assertEquals(1, rootCause.unsatisfied.size());
+        assertTrue(rootCause.unsatisfied.get(0).candidates.isEmpty());
     }
 
 }
diff --git a/rootcause/src/test/java/org/apache/felix/rootcause/util/BaseTest.java b/rootcause/src/test/java/org/apache/felix/rootcause/util/BaseTest.java
index 17bd4f8..a3dfe8e 100644
--- a/rootcause/src/test/java/org/apache/felix/rootcause/util/BaseTest.java
+++ b/rootcause/src/test/java/org/apache/felix/rootcause/util/BaseTest.java
@@ -51,19 +51,21 @@ public class BaseTest {
                 mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").version("1.7.6"),
                 mavenBundle().groupId("ch.qos.logback").artifactId("logback-core").version("1.0.13"),
                 mavenBundle().groupId("ch.qos.logback").artifactId("logback-classic").version("1.0.13"),
-                
+
                 bundle("link:classpath:META-INF/links/org.ops4j.pax.tipi.junit.link"),
                 bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link"),
                 mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.hamcrest").version("1.3_1"),
                 mavenBundle().groupId("org.awaitility").artifactId("awaitility").version("3.1.0"),
 
-                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").version("2.0.14"),
-                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").version("1.8.16"),
+                mavenBundle().groupId("org.osgi").artifactId("org.osgi.util.function").version("1.1.0"),
+                mavenBundle().groupId("org.osgi").artifactId("org.osgi.util.promise").version("1.1.1"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").version("2.1.26"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").version("1.9.22"),
                 bundle("reference:file:target/classes/")
 
         );
     }
-    
+
     public ComponentDescriptionDTO getComponentDesc(String compName) {
         return getComponentDesc(desc -> desc.name.equals(compName), compName);
     }