SLING-8986 osgi-mock: Incorrect selection of fields with assignable types for Set references
diff --git a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
index 8726f9d..27135b9 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
@@ -25,9 +25,12 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.SortedSet;
+import java.util.TreeSet;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.impl.inject.Annotations;
@@ -37,6 +40,7 @@
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicy;
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicyOption;
+import org.jetbrains.annotations.NotNull;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
@@ -351,7 +355,22 @@
}
return null;
}
-
+
+ private static Field getCollectionField(Class clazz, String fieldName) {
+ Field[] fields = clazz.getDeclaredFields();
+ for (Field field : fields) {
+ if (StringUtils.equals(field.getName(), fieldName) && Collection.class.isAssignableFrom(field.getType())) {
+ return field;
+ }
+ }
+ // not found? check super classes
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass != null && superClass != Object.class) {
+ return getCollectionField(superClass, fieldName);
+ }
+ return null;
+ }
+
private static void setField(Object target, Field field, Object value) {
try {
field.setAccessible(true);
@@ -494,20 +513,7 @@
item = serviceInfo.getServiceReference();
}
}
- // 1. collection
- Field field = getFieldWithAssignableType(targetClass, fieldName, Collection.class);
- if (field != null) {
- if (bind) {
- addToCollection(target, field, item);
- }
- else {
- removeFromCollection(target, field, item);
- }
- return;
- }
-
- // 2. list
- field = getField(targetClass, fieldName, List.class);
+ Field field = getCollectionField(targetClass, fieldName);
if (field != null) {
if (bind) {
addToCollection(target, field, item);
@@ -551,17 +557,14 @@
field.setAccessible(true);
Collection<Object> collection = (Collection<Object>)field.get(target);
if (collection == null) {
- collection = new ArrayList<Object>();
+ collection = newCollectionInstance(field.getType());
}
if (item != null) {
collection.add(item);
}
field.set(target, collection);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
- + target.getClass().getName(), ex);
- } catch (IllegalArgumentException ex) {
+ } catch (IllegalAccessException | IllegalArgumentException | InstantiationException ex) {
throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
+ target.getClass().getName(), ex);
}
@@ -573,22 +576,34 @@
field.setAccessible(true);
Collection<Object> collection = (Collection<Object>)field.get(target);
if (collection == null) {
- collection = new ArrayList<Object>();
+ collection = newCollectionInstance(field.getType());
}
if (item != null) {
collection.remove(item);
}
field.set(target, collection);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
- + target.getClass().getName(), ex);
- } catch (IllegalArgumentException ex) {
+ } catch (IllegalAccessException | IllegalArgumentException | InstantiationException ex) {
throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
+ target.getClass().getName(), ex);
}
}
+ @SuppressWarnings({ "unchecked", "null" })
+ private static @NotNull Collection<Object> newCollectionInstance(Class<?> collectionType)
+ throws InstantiationException, IllegalAccessException {
+ if (collectionType == List.class) {
+ return new ArrayList<>();
+ }
+ if (collectionType == Set.class) {
+ return new HashSet<>();
+ }
+ if (collectionType == SortedSet.class) {
+ return new TreeSet();
+ }
+ return (Collection)collectionType.newInstance();
+ }
+
/**
* Directly invoke bind method on service for the given reference.
* @param reference Reference metadata
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
index 896d68f..b6387b9 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
@@ -66,7 +66,7 @@
public void testReferences() {
OsgiMetadata metadata = OsgiMetadataUtil.getMetadata(OsgiServiceUtilTest.Service3.class);
List<Reference> references = metadata.getReferences();
- assertEquals(4, references.size());
+ assertEquals(5, references.size());
Reference ref1 = references.get(2);
assertEquals("reference2", ref1.getName());
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
index a962daa..e22a64e 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
@@ -27,9 +27,11 @@
import java.util.ArrayList;
import java.util.Dictionary;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.junit.After;
import org.junit.Before;
@@ -97,6 +99,40 @@
assertEquals(1, reference3Configs.size());
assertEquals(200, reference3Configs.get(0).get(Constants.SERVICE_RANKING));
+ Set<ServiceSuperInterface3> references3Set = service3.getReferences3Set();
+ assertEquals(1, references3Set.size());
+ assertSame(service2, references3Set.iterator().next());
+
+ assertTrue(MockOsgi.deactivate(service3, bundleContext));
+ assertNull(service3.getComponentContext());
+ }
+
+ @Test
+ public void testService3OsgiR6() {
+ Service3OsgiR6 service3 = new Service3OsgiR6();
+ assertTrue(MockOsgi.injectServices(service3, bundleContext));
+
+ Dictionary<String, Object> service3Config = new Hashtable<String, Object>();
+ service3Config.put("prop1", "value1");
+ assertTrue(MockOsgi.activate(service3, bundleContext, service3Config));
+
+ assertNotNull(service3.getComponentContext());
+ assertEquals(service3Config.get("prop1"), service3.getComponentContext().getProperties().get("prop1"));
+
+ assertSame(service1, service3.getReference1());
+
+ List<ServiceInterface2> references2 = service3.getReferences2();
+ assertEquals(1, references2.size());
+ assertSame(service2, references2.get(0));
+
+ List<ServiceSuperInterface3> references3 = service3.getReferences3();
+ assertEquals(1, references3.size());
+ assertSame(service2, references3.get(0));
+
+ Set<ServiceSuperInterface3> references3Set = service3.getReferences3Set();
+ assertEquals(1, references3Set.size());
+ assertSame(service2, references3Set.iterator().next());
+
assertTrue(MockOsgi.deactivate(service3, bundleContext));
assertNull(service3.getComponentContext());
}
@@ -241,12 +277,16 @@
@Reference(cardinality = ReferenceCardinality.OPTIONAL, bind="bindReference1Optional", unbind="unbindReference1Optional")
private ServiceInterface1Optional reference1Optional;
- private List<ServiceReference> references2 = new ArrayList<ServiceReference>();
+ private List<ServiceReference> references2 = new ArrayList<>();
@Reference(name = "reference3", service = ServiceInterface3.class, cardinality = ReferenceCardinality.MULTIPLE,
bind="bindReference3", unbind="unbindReference3")
- private List<ServiceSuperInterface3> references3 = new ArrayList<ServiceSuperInterface3>();
- private List<Map<String, Object>> reference3Configs = new ArrayList<Map<String, Object>>();
+ private List<ServiceSuperInterface3> references3 = new ArrayList<>();
+ private List<Map<String, Object>> reference3Configs = new ArrayList<>();
+
+ @Reference(name = "references3Set", service = ServiceInterface3.class, cardinality = ReferenceCardinality.MULTIPLE,
+ bind="bindReferenceSet3", unbind="unbindReferenceSet3")
+ private Set<ServiceSuperInterface3> references3Set = new HashSet<>();
private ComponentContext componentContext;
private Map<String, Object> config;
@@ -276,7 +316,7 @@
}
public List<ServiceInterface2> getReferences2() {
- List<ServiceInterface2> services = new ArrayList<ServiceInterface2>();
+ List<ServiceInterface2> services = new ArrayList<>();
for (ServiceReference<?> serviceReference : references2) {
services.add((ServiceInterface2)componentContext.getBundleContext().getService(serviceReference));
}
@@ -291,6 +331,10 @@
return this.reference3Configs;
}
+ public Set<ServiceSuperInterface3> getReferences3Set() {
+ return this.references3Set;
+ }
+
public ComponentContext getComponentContext() {
return this.componentContext;
}
@@ -333,6 +377,14 @@
reference3Configs.remove(serviceConfig);
}
+ protected void bindReference3Set(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) {
+ references3Set.add(service);
+ }
+
+ protected void unbindReference3Set(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) {
+ references3Set.remove(service);
+ }
+
}
public static class Service3OsgiR6 {
@@ -343,6 +395,7 @@
private List<ServiceSuperInterface3> references3;
private List<ServiceSuperInterface3> references3Filtered;
private ServiceSuperInterface3 reference3DynamicFiltered;
+ private Set<ServiceSuperInterface3> references3Set;
private ComponentContext componentContext;
private Map<String, Object> config;
@@ -392,6 +445,10 @@
return this.reference3DynamicFiltered;
}
+ public Set<ServiceSuperInterface3> getReferences3Set() {
+ return this.references3Set;
+ }
+
public ComponentContext getComponentContext() {
return this.componentContext;
}
diff --git a/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml b/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
index b4a4266..8bb4422 100644
--- a/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
+++ b/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
@@ -42,6 +42,7 @@
<reference name="reference1Optional" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface1Optional" cardinality="0..1" policy="dynamic" bind="bindReference1Optional" unbind="unbindReference1Optional"/>
<reference name="reference2" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface2" cardinality="1..n" policy="dynamic" bind="bindReference2" unbind="unbindReference2"/>
<reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" bind="bindReference3" unbind="unbindReference3"/>
+ <reference name="reference3Set" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3" bind="bindReference3Set" unbind="unbindReference3Set"/>
</scr:component>
<scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3OsgiR6" activate="activate" deactivate="deactivate" modified="modified">
<implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3OsgiR6"/>
@@ -52,6 +53,7 @@
<reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3" field-collection-type="service"/>
<reference name="references3Filtered" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3Filtered" field-collection-type="service" target="(prop1=abc)"/>
<reference name="reference3DynamicFiltered" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..1" policy="dynamic" field="reference3DynamicFiltered" field-collection-type="service"/>
+ <reference name="reference3Set" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3Set" field-collection-type="service"/>
</scr:component>
<scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedy" activate="activate" deactivate="deactivate" modified="modified">
<implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedyImpl"/>