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 1879341adf5a7169c50d91be491b101d935dac8b
Author: Julian Sedding <[email protected]>
AuthorDate: Mon Jan 8 23:04:06 2024 +0100

    refactor: split Extensions into dedicated classes
---
 .../apache/sling/testing/osgi/unit/Closeables.java |  52 +++++
 .../sling/testing/osgi/unit/OSGiSupport.java       |   4 +-
 ...tImpl.java => OSGiSupportFrameworkHandler.java} | 225 +++------------------
 .../unit/OSGiSupportInvocationInterceptor.java     | 165 +++++++++++++++
 .../osgi/unit/OSGiSupportParameterResolver.java    |  54 +++++
 5 files changed, 297 insertions(+), 203 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/osgi/unit/Closeables.java 
b/src/main/java/org/apache/sling/testing/osgi/unit/Closeables.java
new file mode 100644
index 00000000..8549a978
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/osgi/unit/Closeables.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.osgi.unit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+class Closeables {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(Closeables.class);
+
+    private final Collection<Closeable> closeables;
+
+    Closeables() {
+        closeables = new ArrayList<>();
+    }
+
+    void add(Closeable closeable) {
+        closeables.add(closeable);
+    }
+
+    public void closeAll() {
+        for (Closeable closeable : closeables) {
+            try {
+                closeable.close();
+            } catch (IOException e) {
+                LOG.warn("Failed to close " + closeable, e);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupport.java 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupport.java
index 900605b8..51743a1b 100644
--- a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupport.java
+++ b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupport.java
@@ -29,7 +29,9 @@ import java.util.List;
 
 @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE })
 @Retention(RetentionPolicy.RUNTIME)
-@ExtendWith(OSGiSupportImpl.class)
+@ExtendWith(OSGiSupportFrameworkHandler.class)
+@ExtendWith(OSGiSupportParameterResolver.class)
+@ExtendWith(OSGiSupportInvocationInterceptor.class)
 public @interface OSGiSupport {
 
     Enablement verbose() default Enablement.INHERIT;
diff --git 
a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportFrameworkHandler.java
similarity index 54%
rename from 
src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java
rename to 
src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportFrameworkHandler.java
index 28f41645..126d78c5 100644
--- a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportImpl.java
+++ 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportFrameworkHandler.java
@@ -27,23 +27,13 @@ import biz.aQute.resolve.ResolverLogger;
 import org.apache.sling.testing.osgi.unit.impl.BundleUtil;
 import org.apache.sling.testing.osgi.unit.impl.OSGiUnitConfig;
 import org.apache.sling.testing.osgi.unit.impl.OSGiUtil;
-import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
 import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.InvocationInterceptor;
-import org.junit.jupiter.api.extension.ParameterContext;
-import org.junit.jupiter.api.extension.ParameterResolutionException;
-import org.junit.jupiter.api.extension.ParameterResolver;
-import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
 import org.junit.platform.commons.support.AnnotationSupport;
-import org.junit.platform.commons.support.ReflectionSupport;
 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;
 import org.osgi.framework.startlevel.BundleStartLevel;
@@ -55,19 +45,12 @@ import org.osgi.service.resolver.Resolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.Closeable;
 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;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -85,149 +68,14 @@ import static java.util.function.Predicate.not;
 import static 
org.apache.sling.testing.osgi.unit.impl.BundleUtil.collectJarFilesFromClassPath;
 import static org.osgi.framework.Constants.FRAMEWORK_STORAGE;
 
-public class OSGiSupportImpl implements BeforeTestExecutionCallback, 
AfterTestExecutionCallback, InvocationInterceptor, ParameterResolver {
+class OSGiSupportFrameworkHandler implements BeforeTestExecutionCallback, 
AfterTestExecutionCallback {
 
-    private static final Logger LOG = 
LoggerFactory.getLogger(OSGiSupportImpl.class);
+    private static final Logger LOG = 
LoggerFactory.getLogger(OSGiSupportFrameworkHandler.class);
 
-    private static final ExtensionContext.Namespace namespace = 
ExtensionContext.Namespace.create(OSGiSupportImpl.class);
+    private static final ExtensionContext.Namespace namespace = 
ExtensionContext.Namespace.create(OSGiSupportInvocationInterceptor.class);
 
-    @Override
-    public void interceptTestMethod(Invocation<Void> invocation, 
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext 
extensionContext) throws Throwable {
-        invokeWithinContextOfOSGiFramework(invocationContext, 
extensionContext);
-        invocation.skip();
-    }
-
-    @Override
-    public void interceptTestTemplateMethod(Invocation<Void> invocation, 
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext 
extensionContext) throws Throwable {
-        invokeWithinContextOfOSGiFramework(invocationContext, 
extensionContext);
-        invocation.skip();
-    }
-
-    private static void 
invokeWithinContextOfOSGiFramework(ReflectiveInvocationContext<Method> 
invocationContext, ExtensionContext extensionContext) throws 
ClassNotFoundException, NoSuchMethodException {
-        final ExtensionContext.Store store = getStore(extensionContext);
-        final Bundle bundle = store.get(Bundle.class, Bundle.class);
-        final Method method = invocationContext.getExecutable();
-
-        final Class<?>[] parameterTypes = method.getParameterTypes();
-        final Class<?>[] parameterTypesInOsgi = new 
Class<?>[parameterTypes.length];
-        for (int i = 0; i < parameterTypesInOsgi.length; i++) {
-            parameterTypesInOsgi[i] = 
bundle.loadClass(parameterTypes[i].getName());
-        }
-
-        final Class<?> targetClassInOsgi = 
bundle.loadClass(invocationContext.getTargetClass().getName());
-        final Method methodInOsgi = 
targetClassInOsgi.getDeclaredMethod(method.getName(), parameterTypesInOsgi);
-
-        final Service[] serviceAnnotations = store.get(Service.class, 
Service[].class);
-        final Object[] arguments = new Object[parameterTypesInOsgi.length];
-        final Parameter[] parameters = methodInOsgi.getParameters();
-        for (int i = 0; i < arguments.length; i++) {
-            if (serviceAnnotations[i] != null) {
-                arguments[i] = resolveServiceObject(serviceAnnotations[i], 
store, parameters[i]);
-            } else {
-                arguments[i] = invocationContext.getArguments().get(i);
-            }
-        }
-
-        final Object instanceInOSGi = 
ReflectionSupport.newInstance(targetClassInOsgi);
-        ReflectionSupport.invokeMethod(methodInOsgi, instanceInOSGi, 
arguments);
-    }
-
-    @Override
-    public boolean supportsParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
-        return 
Objects.nonNull(getStore(extensionContext).get(parameterContext.getParameter().getType()))
-                || parameterContext.findAnnotation(Service.class).isPresent();
-    }
-
-    @Override
-    public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
-        final ExtensionContext.Store store = getStore(extensionContext);
-        final Parameter parameter = parameterContext.getParameter();
-        final Class<?> parameterType = parameter.getType();
-        final Service[] serviceAnnotations = 
store.getOrComputeIfAbsent(Service.class,
-                k -> new 
Service[parameterContext.getDeclaringExecutable().getParameterCount()],
-                Service[].class);
-        final Optional<Service> serviceAnnotation = 
parameterContext.findAnnotation(Service.class);
-        if (serviceAnnotation.isPresent()) {
-            // services need to be resolved late, because they may be loaded 
by different class loaders in OSGi
-            serviceAnnotations[parameterContext.getIndex()] = 
serviceAnnotation.get();
-            return null;
-        }
-        return store.get(parameterType);
-    }
-
-    @Nullable
-    private static Object resolveServiceObject(Service service, 
ExtensionContext.Store store, Parameter parameter) {
-        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 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 {
-            serviceType = wrapperType;
-            transformer = bc::getService;
-            closeableFactory = ref -> () -> bc.ungetService(ref);
-        }
-
-        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(closeableFactory.apply(ref));
-                        return transformer.apply(ref);
-                    });
-        } catch (InvalidSyntaxException e) {
-            throw new ParameterResolutionException(
-                    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();
+    static ExtensionContext.Store getStore(ExtensionContext context) {
+        return context.getStore(namespace);
     }
 
     @Override
@@ -313,27 +161,6 @@ public class OSGiSupportImpl implements 
BeforeTestExecutionCallback, AfterTestEx
         getStore(context).put(BundleContext.class, 
testBundle.getBundleContext());
     }
 
-    private static String osgiIdentities(String bundleSymbolicName, 
Collection<String> loggerBundles, Collection<String> additionalBundles) {
-        return Stream.of(Stream.of(bundleSymbolicName), 
loggerBundles.stream(), additionalBundles.stream())
-                .flatMap(Function.identity())
-                .map(bsn -> 
String.format("osgi.identity;filter:='(osgi.identity=%s)'", bsn))
-                .collect(Collectors.joining(","));
-    }
-
-    private static Optional<Bundle> installBundle(Framework framework, 
Resource resource) throws IOException, BundleException {
-        return ResourceUtils.getURI(resource)
-                .map(uri -> {
-                    try (InputStream bundleStream = 
Files.newInputStream(Path.of(uri))) {
-                        final Bundle bundle = 
framework.getBundleContext().installBundle(uri.toString(), bundleStream);
-                        bundle.adapt(BundleStartLevel.class).setStartLevel(5);
-                        LOG.info("Installed bundle '{}-{}'", 
bundle.getSymbolicName(), bundle.getVersion());
-                        return bundle;
-                    } catch (IOException | BundleException e) {
-                        throw new IllegalStateException(e);
-                    }
-                });
-    }
-
     @Override
     public void afterTestExecution(ExtensionContext context) throws Exception {
         final ExtensionContext.Store store = getStore(context);
@@ -348,10 +175,6 @@ public class OSGiSupportImpl implements 
BeforeTestExecutionCallback, AfterTestEx
         }
     }
 
-    private static ExtensionContext.Store getStore(ExtensionContext context) {
-        return context.getStore(namespace);
-    }
-
     private static Map<Resource, List<Wire>> 
resolveBundlesOnClasspath(ClassLoader classLoader, String runRequires, Path... 
testBundles) throws Exception {
         try (final ResolverLogger logger = new ResolverLogger(); final 
Processor processor = new Processor()) {
             final List<Path> jarFiles = 
collectJarFilesFromClassPath(classLoader);
@@ -388,26 +211,24 @@ public class OSGiSupportImpl implements 
BeforeTestExecutionCallback, AfterTestEx
                 .collect(Collectors.toUnmodifiableList());
     }
 
-    private static class Closeables {
-
-        private final Collection<Closeable> closeables;
-
-        Closeables() {
-            closeables = new ArrayList<>();
-        }
-
-        void add(Closeable closeable) {
-            closeables.add(closeable);
-        }
+    private static String osgiIdentities(String bundleSymbolicName, 
Collection<String> loggerBundles, Collection<String> additionalBundles) {
+        return Stream.of(Stream.of(bundleSymbolicName), 
loggerBundles.stream(), additionalBundles.stream())
+                .flatMap(Function.identity())
+                .map(bsn -> 
String.format("osgi.identity;filter:='(osgi.identity=%s)'", bsn))
+                .collect(Collectors.joining(","));
+    }
 
-        void closeAll() {
-            for (Closeable closeable : closeables) {
-                try {
-                    closeable.close();
-                } catch (IOException e) {
-                    LOG.warn("Failed to close " + closeable, e);
-                }
-            }
-        }
+    private static Optional<Bundle> installBundle(Framework framework, 
Resource resource) throws IOException, BundleException {
+        return ResourceUtils.getURI(resource)
+                .map(uri -> {
+                    try (InputStream bundleStream = 
Files.newInputStream(Path.of(uri))) {
+                        final Bundle bundle = 
framework.getBundleContext().installBundle(uri.toString(), bundleStream);
+                        bundle.adapt(BundleStartLevel.class).setStartLevel(5);
+                        LOG.info("Installed bundle '{}-{}'", 
bundle.getSymbolicName(), bundle.getVersion());
+                        return bundle;
+                    } catch (IOException | BundleException e) {
+                        throw new IllegalStateException(e);
+                    }
+                });
     }
 }
diff --git 
a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportInvocationInterceptor.java
 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportInvocationInterceptor.java
new file mode 100644
index 00000000..20c430eb
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportInvocationInterceptor.java
@@ -0,0 +1,165 @@
+/*
+ * 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.osgi.unit;
+
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+import org.junit.platform.commons.support.ReflectionSupport;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceObjects;
+import org.osgi.framework.ServiceReference;
+
+import java.io.Closeable;
+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.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.function.Predicate.not;
+
+public class OSGiSupportInvocationInterceptor implements InvocationInterceptor 
{
+
+    @Override
+    public void interceptTestMethod(Invocation<Void> invocation, 
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext 
extensionContext) throws Throwable {
+        invokeWithinContextOfOSGiFramework(invocationContext, 
extensionContext);
+        invocation.skip();
+    }
+
+    @Override
+    public void interceptTestTemplateMethod(Invocation<Void> invocation, 
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext 
extensionContext) throws Throwable {
+        invokeWithinContextOfOSGiFramework(invocationContext, 
extensionContext);
+        invocation.skip();
+    }
+
+    private static void 
invokeWithinContextOfOSGiFramework(ReflectiveInvocationContext<Method> 
invocationContext, ExtensionContext extensionContext) throws 
ClassNotFoundException, NoSuchMethodException {
+        final ExtensionContext.Store store = 
OSGiSupportFrameworkHandler.getStore(extensionContext);
+        final Bundle bundle = store.get(Bundle.class, Bundle.class);
+        final Method method = invocationContext.getExecutable();
+
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        final Class<?>[] parameterTypesInOsgi = new 
Class<?>[parameterTypes.length];
+        for (int i = 0; i < parameterTypesInOsgi.length; i++) {
+            parameterTypesInOsgi[i] = 
bundle.loadClass(parameterTypes[i].getName());
+        }
+
+        final Class<?> targetClassInOsgi = 
bundle.loadClass(invocationContext.getTargetClass().getName());
+        final Method methodInOsgi = 
targetClassInOsgi.getDeclaredMethod(method.getName(), parameterTypesInOsgi);
+
+        final Service[] serviceAnnotations = store.get(Service.class, 
Service[].class);
+        final Object[] arguments = new Object[parameterTypesInOsgi.length];
+        final Parameter[] parameters = methodInOsgi.getParameters();
+        for (int i = 0; i < arguments.length; i++) {
+            if (serviceAnnotations[i] != null) {
+                arguments[i] = resolveServiceObject(serviceAnnotations[i], 
store, parameters[i]);
+            } else {
+                arguments[i] = invocationContext.getArguments().get(i);
+            }
+        }
+
+        final Object instanceInOSGi = 
ReflectionSupport.newInstance(targetClassInOsgi);
+        ReflectionSupport.invokeMethod(methodInOsgi, instanceInOSGi, 
arguments);
+    }
+
+    @Nullable
+    private static Object resolveServiceObject(Service service, 
ExtensionContext.Store store, Parameter parameter) {
+        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 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 {
+            serviceType = wrapperType;
+            transformer = bc::getService;
+            closeableFactory = ref -> () -> bc.ungetService(ref);
+        }
+
+        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(closeableFactory.apply(ref));
+                        return transformer.apply(ref);
+                    });
+        } catch (InvalidSyntaxException e) {
+            throw new ParameterResolutionException(
+                    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();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportParameterResolver.java
 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportParameterResolver.java
new file mode 100644
index 00000000..5b32e80d
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/testing/osgi/unit/OSGiSupportParameterResolver.java
@@ -0,0 +1,54 @@
+/*
+ * 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.osgi.unit;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+import java.lang.reflect.Parameter;
+import java.util.Objects;
+import java.util.Optional;
+
+class OSGiSupportParameterResolver implements ParameterResolver {
+
+    @Override
+    public boolean supportsParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
+        return 
Objects.nonNull(OSGiSupportFrameworkHandler.getStore(extensionContext).get(parameterContext.getParameter().getType()))
+                || parameterContext.findAnnotation(Service.class).isPresent();
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
+        final ExtensionContext.Store store = 
OSGiSupportFrameworkHandler.getStore(extensionContext);
+        final Parameter parameter = parameterContext.getParameter();
+        final Class<?> parameterType = parameter.getType();
+        final Service[] serviceAnnotations = 
store.getOrComputeIfAbsent(Service.class,
+                k -> new 
Service[parameterContext.getDeclaringExecutable().getParameterCount()],
+                Service[].class);
+        final Optional<Service> serviceAnnotation = 
parameterContext.findAnnotation(Service.class);
+        if (serviceAnnotation.isPresent()) {
+            // services need to be resolved late, because they may be loaded 
by different class loaders in OSGi
+            serviceAnnotations[parameterContext.getIndex()] = 
serviceAnnotation.get();
+            return null;
+        }
+        return store.get(parameterType);
+    }
+}

Reply via email to