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)
