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;
+    }
+
+}