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