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

jsedding pushed a commit to branch org.apache.sling.testing.osgi.unit
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git

commit 9b6321008a7db0c8014488ce296209ba894657e1
Author: Julian Sedding <[email protected]>
AuthorDate: Mon Jan 8 22:46:16 2024 +0100

    add support for ServiceReference and ServiceObjects injection
---
 .../sling/testing/osgi/unit/OSGiSupportImpl.java   | 88 ++++++++++++++++------
 .../testing/osgi/unit/impl/OSGiSupportTest.java    | 74 +++++++++++++++++-
 2 files changed, 135 insertions(+), 27 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java
index eb93186d..28f41645 100644
--- a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java
+++ b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java
@@ -42,6 +42,7 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceObjects;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
@@ -59,9 +60,11 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
 import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -154,44 +157,79 @@ public class OSGiSupportImpl implements 
BeforeTestExecutionCallback, AfterTestEx
 
     @Nullable
     private static Object resolveServiceObject(Service service, 
ExtensionContext.Store store, Parameter parameter) {
-        final Optional<String> filter = 
Optional.ofNullable(service.filter()).filter(not(String::isBlank));
+        final String filter = 
Optional.ofNullable(service.filter()).filter(not(String::isBlank)).orElse(null);
+        final Class<?> parameterClass = parameter.getType();
+
+        if (parameterClass.isArray()) {
+            final Type componentType;
+            final Class<?> castType;
+            if (parameter.getParameterizedType() instanceof GenericArrayType) {
+                final GenericArrayType genericArrayType = (GenericArrayType) 
parameter.getParameterizedType();
+                componentType = genericArrayType.getGenericComponentType();
+                castType = (Class<?>) ((ParameterizedType) 
componentType).getRawType();
+            } else {
+                componentType = parameterClass.getComponentType();
+                castType = parameterClass.getComponentType();
+            }
+
+            return resolveServices(store, componentType, filter)
+                    .toArray(i -> (Object[]) Array.newInstance(castType, i));
+        } else if (parameterClass.isAssignableFrom(List.class)) {
+            final Type genericType = 
getParameterizedType(parameter).getActualTypeArguments()[0];
+            return resolveServices(store, genericType, 
filter).collect(Collectors.toUnmodifiableList());
+        } else {
+            return resolveServices(store, parameter.getParameterizedType(), 
filter).findFirst().orElse(null);
+        }
+    }
+
+    private static Stream<?> resolveServices(ExtensionContext.Store store, 
Type wrapperType, String filter) {
         final BundleContext bc = store.get(BundleContext.class, 
BundleContext.class);
-        final Class<?> parameterType = parameter.getType();
-        final String type;
-        if (parameterType.isArray()) {
-            type = parameterType.getComponentType().getName();
-        } else if (parameterType.isAssignableFrom(List.class)) {
-            type = ((ParameterizedType) 
parameter.getParameterizedType()).getActualTypeArguments()[0].getTypeName();
+
+        final Type serviceType;
+        final Function<ServiceReference<?>, ?> transformer;
+        final Function<ServiceReference<?>, Closeable> closeableFactory;
+
+        if (wrapperType instanceof ParameterizedType) {
+            final ParameterizedType type = (ParameterizedType) wrapperType;
+            final Class<?> wrapperClass = (Class<?>) type.getRawType();
+            if (ServiceReference.class.isAssignableFrom(wrapperClass)) {
+                serviceType = type.getActualTypeArguments()[0];
+                transformer = Function.identity();
+                closeableFactory = ref -> () -> {};
+            } else if (ServiceObjects.class.isAssignableFrom(wrapperClass)) {
+                serviceType = type.getActualTypeArguments()[0];
+                transformer = bc::getServiceObjects;
+                closeableFactory = ref -> () -> {};
+            } else {
+                throw new UnsupportedOperationException("Unsupported wrapper 
type " + wrapperType);
+            }
         } else {
-            type = parameterType.getName();
+            serviceType = wrapperType;
+            transformer = bc::getService;
+            closeableFactory = ref -> () -> bc.ungetService(ref);
         }
-        try {
-            // use string representation of class in order to avoid issues
-            // when mixing classes from different class loaders
-            final ServiceReference<?>[] serviceReferences =
-                    bc.getAllServiceReferences(type, filter.orElse(null));
 
-            final Stream<?> serviceStream = 
Optional.ofNullable(serviceReferences).stream()
+        final String objectClass = serviceType.getTypeName();
+        try {
+            final ServiceReference<?>[] serviceReferences = 
bc.getAllServiceReferences(objectClass, filter);
+            return Optional.ofNullable(serviceReferences).stream()
                     .flatMap(Stream::of)
                     .map(ref -> {
                         store.getOrComputeIfAbsent(Closeables.class, k -> new 
Closeables(), Closeables.class)
-                                .add(() -> bc.ungetService(ref));
-                        return bc.getService(ref);
+                                .add(closeableFactory.apply(ref));
+                        return transformer.apply(ref);
                     });
-
-            if (parameterType.isArray()) {
-                return serviceStream.toArray(i -> (Object[]) 
Array.newInstance(parameterType.getComponentType(), i));
-            } else if (parameterType.isAssignableFrom(List.class)) {
-                return serviceStream.collect(Collectors.toUnmodifiableList());
-            } else {
-                return serviceStream.findFirst().orElse(null);
-            }
         } catch (InvalidSyntaxException e) {
             throw new ParameterResolutionException(
-                    String.format("Failed to retrieve service of type %s%s", 
type, filter.map(f -> "with filter " + f).orElse("")), e);
+                    String.format("Failed to retrieve service of type %s%s",
+                            objectClass, filter == null ? "" : "with filter " 
+ filter), e);
         }
     }
 
+    private static ParameterizedType getParameterizedType(Parameter parameter) 
{
+        return (ParameterizedType) parameter.getParameterizedType();
+    }
+
     @Override
     public void beforeTestExecution(ExtensionContext context) throws Exception 
{
         final Class<?> testClass = context.getRequiredTestClass();
diff --git 
a/src/test/java/org/apache/sling/testing/osgi/unit/impl/OSGiSupportTest.java 
b/src/test/java/org/apache/sling/testing/osgi/unit/impl/OSGiSupportTest.java
index 74585bb7..79ff3ebf 100644
--- a/src/test/java/org/apache/sling/testing/osgi/unit/impl/OSGiSupportTest.java
+++ b/src/test/java/org/apache/sling/testing/osgi/unit/impl/OSGiSupportTest.java
@@ -32,6 +32,8 @@ import org.junit.jupiter.params.provider.ValueSource;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceObjects;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.service.component.annotations.Component;
@@ -53,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.fail;
 import static org.osgi.framework.wiring.BundleWiring.LISTRESOURCES_LOCAL;
 
 @OSGiSupport(verbose = ENABLED)
@@ -184,6 +187,75 @@ class OSGiSupportTest {
                 .contains(control);
     }
 
+    @Test
+    void serviceReferenceInjection(
+            @Service ServiceReference<Condition> serviceReference,
+            @Service ServiceReference<Condition>[] serviceReferencesArray,
+            @Service List<ServiceReference<Condition>> serviceReferencesList) 
throws Throwable {
+        assertThat(serviceReference)
+                .isNotNull()
+                .matches(ref -> withService(ref, Condition.class::isInstance));
+
+        assertThat(serviceReferencesArray)
+                .isNotNull()
+                .hasSizeGreaterThan(0)
+                .allMatch(reference -> withService(reference, 
Condition.class::isInstance));
+
+        assertThat(serviceReferencesList)
+                .isNotNull()
+                .hasSizeGreaterThan(0)
+                .allMatch(reference -> withService(reference, 
Condition.class::isInstance));
+    }
+
+    @Test
+    void serviceObjectsInjection(
+            @Service ServiceObjects<Condition> serviceObject,
+            @Service ServiceObjects<Condition>[] serviceObjectsArray,
+            @Service List<ServiceObjects<Condition>> serviceObjectsList) {
+        assertThat(serviceObject)
+                .isNotNull()
+                .matches(so -> withService(so, Condition.class::isInstance));
+
+        assertThat(serviceObjectsArray)
+                .isNotNull()
+                .hasSizeGreaterThan(0)
+                .allMatch(reference -> withService(reference, 
Condition.class::isInstance));
+
+        assertThat(serviceObjectsList)
+                .isNotNull()
+                .hasSizeGreaterThan(0)
+                .allMatch(reference -> withService(reference, 
Condition.class::isInstance));
+    }
+
+    private interface ThrowingPredicate<T> {
+        boolean test(T object) throws Throwable;
+    }
+
+    private static <T> boolean withService(ServiceObjects<T> serviceObjects, 
ThrowingPredicate<T> servicePredicate) {
+        final T service = serviceObjects.getService();
+        try {
+            return servicePredicate.test(service);
+        } catch (Throwable t) {
+            fail(t);
+            return false;
+        } finally {
+            serviceObjects.ungetService(service);
+        }
+    }
+
+    private static <T> boolean withService(ServiceReference<T> 
serviceReference, ThrowingPredicate<T> servicePredicate) {
+        final BundleContext bc = 
serviceReference.getBundle().getBundleContext();
+        try {
+            final T service = bc.getService(serviceReference);
+            return servicePredicate.test(service);
+        } catch (Throwable t) {
+            fail(t);
+            return false;
+        } finally {
+            bc.ungetService(serviceReference);
+        }
+    }
+
     @Test
     void ignoreUnsupportedInjections(Framework framework, @TempDir Path path, 
Bundle bundle, @TempDir File file, @Service Condition condition) {
         assertNotNull(framework);
@@ -223,8 +295,6 @@ class OSGiSupportTest {
         assertThat(testInfo.getDisplayName()).isEqualTo("testInfoSupport");
     }
 
-
-
     @NotNull
     private static List<String> getSymbolicNames(Bundle... bundles) {
         return Stream.of(bundles)

Reply via email to