SLING-7284 Extend OsgiServiceUtil#invokeBindUnbindMethod to support all DS 1.3+ method signatures
diff --git a/core/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java b/core/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
index 12cffa5..b2e761b 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
@@ -124,7 +124,7 @@
Dictionary<String, Object> mergedPropertes = MapMergeUtil.propertiesMergeWithOsgiMetadata(service.getClass(), configAdmin, properties);
MockServiceRegistration<?> registration = new MockServiceRegistration<>(this.bundle, clazzes, service, mergedPropertes, this);
this.registeredServices.add(registration);
- handleRefsUpdateOnRegister(registration);
+ handleRefsUpdateOnRegister(registration, this);
notifyServiceListeners(ServiceEvent.REGISTERED, registration.getReference());
return registration;
}
@@ -138,9 +138,10 @@
/**
* Check for already registered services that may be affected by the service registration - either
* adding by additional optional references, or creating a conflict in the dependencies.
- * @param registration
+ * @param registration Service registration
+ * @param bundleContext Bundle context
*/
- private void handleRefsUpdateOnRegister(MockServiceRegistration<?> registration) {
+ private void handleRefsUpdateOnRegister(MockServiceRegistration<?> registration, BundleContext bundleContext) {
// handle DYNAMIC references to this registration
List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration);
@@ -160,7 +161,7 @@
case OPTIONAL_MULTIPLE:
case OPTIONAL_UNARY:
OsgiServiceUtil.invokeBindMethod(reference, referenceInfo.getServiceRegistration().getService(),
- new ServiceInfo(registration));
+ new ServiceInfo(registration), bundleContext);
break;
default:
throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality());
@@ -189,7 +190,7 @@
void unregisterService(MockServiceRegistration<?> registration) {
this.registeredServices.remove(registration);
- handleRefsUpdateOnUnregister(registration);
+ handleRefsUpdateOnUnregister(registration, this);
notifyServiceListeners(ServiceEvent.UNREGISTERING, registration.getReference());
}
@@ -213,9 +214,10 @@
/**
* Check for already registered services that may be affected by the service unregistration - either
* adding by removing optional references, or creating a conflict in the dependencies.
- * @param registration
+ * @param registration Service registration
+ * @param bundleContext Bundle context
*/
- private void handleRefsUpdateOnUnregister(MockServiceRegistration<?> registration) {
+ private void handleRefsUpdateOnUnregister(MockServiceRegistration<?> registration, BundleContext bundleContext) {
// handle DYNAMIC references to this registration
List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration);
@@ -229,7 +231,7 @@
case OPTIONAL_UNARY:
// it is currently not checked if for a MANDATORY_UNARY or MANDATORY_MULTIPLE reference the last reference is removed
OsgiServiceUtil.invokeUnbindMethod(reference, referenceInfo.getServiceRegistration().getService(),
- new ServiceInfo(registration));
+ new ServiceInfo(registration), bundleContext);
break;
default:
throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality());
@@ -474,6 +476,25 @@
return null;
}
+ @Override
+ public <S> ServiceObjects<S> getServiceObjects(ServiceReference<S> reference) {
+ return new ServiceObjects<S>() {
+ @Override
+ public S getService() {
+ return MockBundleContext.this.getService(reference);
+ }
+ @Override
+ public void ungetService(S service) {
+ MockBundleContext.this.ungetService(reference);
+ }
+ @Override
+ public ServiceReference<S> getServiceReference() {
+ return reference;
+ }
+ };
+ }
+
+
// --- unsupported operations ---
@Override
public Bundle installBundle(final String s) {
@@ -485,9 +506,4 @@
throw new UnsupportedOperationException();
}
- @Override
- public <S> ServiceObjects<S> getServiceObjects(ServiceReference<S> reference) {
- throw new UnsupportedOperationException();
- }
-
}
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 50f2541..0b1a7ba 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
@@ -39,6 +39,7 @@
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.scr.impl.helper.ComponentServiceObjectsHelper;
import org.apache.felix.scr.impl.inject.Annotations;
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.DynamicReference;
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.FieldCollectionType;
@@ -52,6 +53,7 @@
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.ComponentServiceObjects;
/**
* Helper methods to inject dependencies and activate services.
@@ -293,8 +295,21 @@
for (Class<?> parameterType : method.getParameterTypes()) {
boolean foundAnyMatch = false;
for (int i=0; i<types.length; i++) {
- if ((parameterType==types[i])
- || (types[i]==Annotation.class && parameterType.isAnnotation())) {
+ if (types[i] == Annotation.class) {
+ if (parameterType.isAnnotation()) {
+ foundAnyMatch = true;
+ break;
+ }
+ }
+ else if (types[i] == ComponentContext.class || types[i] == BundleContext.class
+ || types[i] == ServiceReference.class || types[i] == ComponentServiceObjects.class
+ || types[i] == Map.class || types[i] == int.class || types[i] == Integer.class) {
+ if (parameterType == types[i]) {
+ foundAnyMatch = true;
+ break;
+ }
+ }
+ else if (parameterType.isAssignableFrom(types[i])) {
foundAnyMatch = true;
break;
}
@@ -610,7 +625,7 @@
}
if (reference.isCardinalityMultiple()) {
// make sure at least empty array is set
- invokeBindUnbindMethod(reference, target, null, true);
+ invokeBindUnbindMethod(reference, target, null, bundleContext, true);
}
}
@@ -629,11 +644,11 @@
// try to invoke bind method
for (ServiceInfo matchingService : matchingServices) {
- invokeBindUnbindMethod(reference, target, matchingService, true);
+ invokeBindUnbindMethod(reference, target, matchingService, bundleContext, true);
}
}
- private static void invokeBindUnbindMethod(Reference reference, Object target, ServiceInfo serviceInfo, boolean bind) {
+ private static void invokeBindUnbindMethod(Reference reference, Object target, ServiceInfo serviceInfo, BundleContext bundleContext, boolean bind) {
Class<?> targetClass = target.getClass();
// try to invoke bind method
@@ -646,6 +661,7 @@
}
if (StringUtils.isNotEmpty(methodName) && serviceInfo != null) {
+ ComponentServiceObjectsHelper helper = new ComponentServiceObjectsHelper(bundleContext);
// 1. ServiceReference
Method method = getMethod(targetClass, methodName, new Class<?>[] { ServiceReference.class });
@@ -654,7 +670,14 @@
return;
}
- // 2. assignable from service instance
+ // 2. ComponentServiceObjects
+ method = getMethod(targetClass, methodName, new Class<?>[] { ComponentServiceObjects.class });
+ if (method != null) {
+ invokeMethod(target, method, new Object[] { helper.getServiceObjects(serviceInfo.getServiceReference()) });
+ return;
+ }
+
+ // 3. assignable from service instance
Class<?> interfaceType = reference.getInterfaceTypeAsClass();
method = getMethodWithAssignableTypes(targetClass, methodName, new Class<?>[] { interfaceType });
if (method != null) {
@@ -662,17 +685,33 @@
return;
}
- // 3. assignable from service instance plus map
- method = getMethodWithAssignableTypes(targetClass, methodName, new Class<?>[] { interfaceType, Map.class });
+ // 4. Map
+ method = getMethod(targetClass, methodName, new Class<?>[] { Map.class });
if (method != null) {
- invokeMethod(target, method, new Object[] { serviceInfo.getServiceInstance(), serviceInfo.getServiceConfig() });
+ invokeMethod(target, method, new Object[] { serviceInfo.getServiceConfig() });
return;
}
- // 4. assignable from service reference plus interface
- method = getMethodWithAssignableTypes(targetClass, methodName, new Class<?>[] {ServiceReference.class, interfaceType});
+ // 5. mixed arguments
+ Class<?>[] mixedArgsAllowed = new Class<?>[] { ServiceReference.class, ComponentServiceObjects.class, interfaceType, Map.class };
+ method = getMethodWithAnyCombinationArgs(targetClass, methodName, mixedArgsAllowed);
if (method != null) {
- invokeMethod(target, method, new Object[] { serviceInfo.getServiceReference(), serviceInfo.getServiceInstance() });
+ Object[] args = new Object[method.getParameterTypes().length];
+ for (int i=0; i<args.length; i++) {
+ if (method.getParameterTypes()[i] == ServiceReference.class) {
+ args[i] = serviceInfo.getServiceReference();
+ }
+ else if (method.getParameterTypes()[i] == ComponentServiceObjects.class) {
+ args[i] = helper.getServiceObjects(serviceInfo.getServiceReference());
+ }
+ else if (method.getParameterTypes()[i].isAssignableFrom(interfaceType)) {
+ args[i] = serviceInfo.getServiceInstance();
+ }
+ else if (method.getParameterTypes()[i] == Map.class) {
+ args[i] = serviceInfo.getServiceConfig();
+ }
+ }
+ invokeMethod(target, method, args);
return;
}
@@ -680,7 +719,7 @@
+ "for reference '" + reference.getName() + "' for class " + targetClass.getName());
}
- // in OSGi declarative services 1.3 there are no bind/unbind methods - modify the field directly
+ // OSGi declarative services 1.3 supports modifying the field directly
else if (StringUtils.isNotEmpty(fieldName)) {
// check for field with list/collection reference
@@ -791,9 +830,10 @@
* @param reference Reference metadata
* @param target Target object for reference
* @param serviceInfo Service on which to invoke the method
+ * @param bundleContext Bundle context
*/
- public static void invokeBindMethod(Reference reference, Object target, ServiceInfo serviceInfo) {
- invokeBindUnbindMethod(reference, target, serviceInfo, true);
+ public static void invokeBindMethod(Reference reference, Object target, ServiceInfo serviceInfo, BundleContext bundleContext) {
+ invokeBindUnbindMethod(reference, target, serviceInfo, bundleContext, true);
}
/**
@@ -801,9 +841,10 @@
* @param reference Reference metadata
* @param target Target object for reference
* @param serviceInfo Service on which to invoke the method
+ * @param bundleContext Bundle context
*/
- public static void invokeUnbindMethod(Reference reference, Object target, ServiceInfo serviceInfo) {
- invokeBindUnbindMethod(reference, target, serviceInfo, false);
+ public static void invokeUnbindMethod(Reference reference, Object target, ServiceInfo serviceInfo, BundleContext bundleContext) {
+ invokeBindUnbindMethod(reference, target, serviceInfo, bundleContext, false);
}
private static List<ServiceInfo> getMatchingServices(Class<?> type, BundleContext bundleContext, String filter) {
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilBindUnbindTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilBindUnbindTest.java
new file mode 100644
index 0000000..dacb3ff
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilBindUnbindTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.sling.testing.mock.osgi;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind.Service1;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind.Service2;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind.Service3;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind.Service4;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind.Service5;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+/**
+ * Test different variants of bind/unbind methods with varying signatures.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class OsgiServiceUtilBindUnbindTest {
+
+ private BundleContext bundleContext;
+
+ private ServiceRegistration<ServiceInterface1> reg1a;
+ private ServiceRegistration<ServiceInterface1> reg1b;
+ private Map<String,Object> props1a = MapUtil.toMap("prop1", "1a");
+ private Map<String,Object> props1b = MapUtil.toMap("prop1", "1b");
+ private Map<String,Object> props1c = MapUtil.toMap("prop1", "1c");
+
+ @Mock
+ private ServiceInterface1 instance1a;
+ @Mock
+ private ServiceInterface1 instance1b;
+ @Mock
+ private ServiceInterface1 instance1c;
+
+ @Before
+ public void setUp() {
+ bundleContext = MockOsgi.newBundleContext();
+ reg1a = registerReference(instance1a, props1a);
+ reg1b = registerReference(instance1b, props1b);
+ }
+
+ @Test
+ public void testService1() {
+ Service1 service = registerInjectService(new Service1());
+ assertItems(service.getInstances(), instance1a, instance1b);
+
+ registerReference(instance1c, props1c);
+ assertItems(service.getInstances(), instance1a, instance1b, instance1c);
+
+ reg1a.unregister();
+ assertItems(service.getInstances(), instance1b, instance1c);
+ }
+
+ @Test
+ public void testService2() {
+ Service2 service = registerInjectService(new Service2());
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference());
+
+ ServiceRegistration<ServiceInterface1> reg1c = registerReference(instance1c, props1c);
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference(), reg1c.getReference());
+
+ reg1a.unregister();
+ assertItems(service.getReferences(), reg1b.getReference(), reg1c.getReference());
+ }
+
+ @Test
+ public void testService3() {
+ Service3 service = registerInjectService(new Service3());
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference());
+ assertItems(service.getInstances(), instance1a, instance1b);
+
+ ServiceRegistration<ServiceInterface1> reg1c = registerReference(instance1c, props1c);
+ assertItems(service.getInstances(), instance1a, instance1b, instance1c);
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference(), reg1c.getReference());
+
+ reg1a.unregister();
+ assertItems(service.getInstances(), instance1b, instance1c);
+ assertItems(service.getReferences(), reg1b.getReference(), reg1c.getReference());
+ }
+
+ @Test
+ public void testService4() {
+ Service4 service = registerInjectService(new Service4());
+ assertMaps(service.getConfigs(), props1a, props1b);
+
+ registerReference(instance1c, props1c);
+ assertMaps(service.getConfigs(), props1a, props1b, props1c);
+
+ reg1a.unregister();
+ assertMaps(service.getConfigs(), props1b, props1c);
+ }
+
+ @Test
+ public void testService5() {
+ Service5 service = registerInjectService(new Service5());
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference());
+ assertItems(service.getInstances(), instance1a, instance1b);
+ assertMaps(service.getConfigs(), props1a, props1b);
+
+ ServiceRegistration<ServiceInterface1> reg1c = registerReference(instance1c, props1c);
+ assertItems(service.getInstances(), instance1a, instance1b, instance1c);
+ assertItems(service.getReferences(), reg1a.getReference(), reg1b.getReference(), reg1c.getReference());
+ assertMaps(service.getConfigs(), props1a, props1b, props1c);
+
+ reg1a.unregister();
+ assertItems(service.getInstances(), instance1b, instance1c);
+ assertItems(service.getReferences(), reg1b.getReference(), reg1c.getReference());
+ assertMaps(service.getConfigs(), props1b, props1c);
+ }
+
+
+ @SuppressWarnings({ "null", "unchecked" })
+ private <T> T registerInjectService(T service) {
+ MockOsgi.injectServices(service, bundleContext);
+ bundleContext.registerService((Class<T>)service.getClass(), service, (Dictionary)null);
+ return service;
+ }
+
+ @SuppressWarnings("null")
+ private <T extends ServiceInterface1> ServiceRegistration<ServiceInterface1> registerReference(T instance, Map<String,Object> props) {
+ return bundleContext.registerService(ServiceInterface1.class, instance, MapUtil.toDictionary(props));
+ }
+
+ @SafeVarargs
+ private final <T> void assertItems(List<T> actual, T... expected) {
+ assertEquals(ImmutableSet.copyOf(expected), ImmutableSet.copyOf(actual));
+ }
+
+ @SafeVarargs
+ @SuppressWarnings("null")
+ private final <T> void assertMaps(List<Map<String,Object>> actual, Map<String,Object>... expected) {
+ List<Map<String,Object>> actualFiltered = actual.stream()
+ .map(actualItem -> Maps.filterEntries(actualItem, item -> item.getKey().equals("prop1")))
+ .collect(Collectors.toList());
+ assertItems(actualFiltered, expected);
+ }
+
+}
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service1.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service1.java
new file mode 100644
index 0000000..1f9db4a
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service1.java
@@ -0,0 +1,50 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@Component
+public class Service1 {
+
+ @Reference(name = "reference1", bind = "bindReference1", unbind = "unbindReference1", service = ServiceInterface1.class,
+ cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+ private volatile List<ServiceInterface1> instances = new ArrayList<>();
+
+ void bindReference1(ServiceInterface1 reference) {
+ instances.add(reference);
+ }
+
+ void unbindReference1(ServiceInterface1 reference) {
+ instances.remove(reference);
+ }
+
+ public List<ServiceInterface1> getInstances() {
+ return instances;
+ }
+
+}
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service2.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service2.java
new file mode 100644
index 0000000..15fc04c
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service2.java
@@ -0,0 +1,51 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@Component
+public class Service2 {
+
+ @Reference(name = "reference1", bind = "bindReference1", unbind = "unbindReference1", service = ServiceInterface1.class,
+ cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+ private volatile List<ServiceReference<ServiceInterface1>> references = new ArrayList<>();
+
+ void bindReference1(ServiceReference<ServiceInterface1> reference) {
+ references.add(reference);
+ }
+
+ void unbindReference1(ServiceReference<ServiceInterface1> reference) {
+ references.remove(reference);
+ }
+
+ public List<ServiceReference<ServiceInterface1>> getReferences() {
+ return references;
+ }
+
+}
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service3.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service3.java
new file mode 100644
index 0000000..fc60fa3
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service3.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentServiceObjects;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@Component(reference = {
+ @Reference(name = "reference1", bind = "bindReference1", unbind = "unbindReference1", service = ServiceInterface1.class,
+ cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+})
+public class Service3 {
+
+ private List<ServiceInterface1> instances = new ArrayList<>();
+ private List<ServiceReference<ServiceInterface1>> references = new ArrayList<>();
+
+ void bindReference1(ComponentServiceObjects<ServiceInterface1> serviceObjects) {
+ instances.add(serviceObjects.getService());
+ references.add(serviceObjects.getServiceReference());
+ }
+
+ void unbindReference1(ComponentServiceObjects<ServiceInterface1> serviceObjects) {
+ instances.remove(serviceObjects.getService());
+ references.remove(serviceObjects.getServiceReference());
+ }
+
+ public List<ServiceInterface1> getInstances() {
+ return instances;
+ }
+
+ public List<ServiceReference<ServiceInterface1>> getReferences() {
+ return references;
+ }
+
+}
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service4.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service4.java
new file mode 100644
index 0000000..3bc08f3
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service4.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@Component(reference = {
+ @Reference(name = "reference1", bind = "bindReference1", unbind = "unbindReference1", service = ServiceInterface1.class,
+ cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+})
+public class Service4 {
+
+ private List<Map<String,Object>> configs = new ArrayList<>();
+
+ void bindReference1(Map<String,Object> config) {
+ configs.add(config);
+ }
+
+ void unbindReference1(Map<String,Object> config) {
+ configs.remove(config);
+ }
+
+ public List<Map<String,Object>> getConfigs() {
+ return configs;
+ }
+
+}
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service5.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service5.java
new file mode 100644
index 0000000..a826964
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/bindunbind/Service5.java
@@ -0,0 +1,74 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil.bindunbind;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentServiceObjects;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@Component(reference = {
+ @Reference(name = "reference1", bind = "bindReference1", unbind = "unbindReference1", service = ServiceInterface1.class,
+ cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+})
+public class Service5 {
+
+ private List<ServiceInterface1> instances = new ArrayList<>();
+ private List<ServiceReference<ServiceInterface1>> references = new ArrayList<>();
+ private List<Map<String,Object>> configs = new ArrayList<>();
+
+ void bindReference1(ServiceInterface1 instance, ServiceReference<ServiceInterface1> reference,
+ ComponentServiceObjects<ServiceInterface1> serviceObjects, Map<String,Object> config) {
+ assert serviceObjects.getService() == instance;
+ assert serviceObjects.getServiceReference() == reference;
+ instances.add(instance);
+ references.add(reference);
+ configs.add(config);
+ }
+
+ void unbindReference1(ComponentServiceObjects<ServiceInterface1> serviceObjects, Map<String,Object> config,
+ ServiceInterface1 instance, ServiceReference<ServiceInterface1> reference) {
+ assert serviceObjects.getService() == instance;
+ assert serviceObjects.getServiceReference() == reference;
+ instances.remove(instance);
+ references.remove(reference);
+ configs.remove(config);
+ }
+
+ public List<ServiceInterface1> getInstances() {
+ return instances;
+ }
+
+ public List<ServiceReference<ServiceInterface1>> getReferences() {
+ return references;
+ }
+
+ public List<Map<String,Object>> getConfigs() {
+ return configs;
+ }
+
+}