This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
commit 1cde3d95b6407f8bfd553cfd25baf9a236760edf Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Sun Jun 14 20:48:16 2020 +0200 [OWB-1330] extract Cdi parameter resolver in its own extension --- .../java/org/apache/openwebbeans/junit5/Cdi.java | 26 ++--- .../openwebbeans/junit5/CdiMethodParameters.java | 39 +++++++ .../org/apache/openwebbeans/junit5/SkipInject.java | 36 ++++++ .../openwebbeans/junit5/internal/CdiExtension.java | 63 +--------- .../internal/CdiParametersResolverExtension.java | 129 +++++++++++++++++++++ .../junit5/CdiParameterResolversTest.java | 96 +++++++++++++++ 6 files changed, 316 insertions(+), 73 deletions(-) diff --git a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/Cdi.java b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/Cdi.java index c6f6f2d..c5c7d7b 100644 --- a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/Cdi.java +++ b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/Cdi.java @@ -23,14 +23,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import java.io.Closeable; import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.function.Supplier; -import javax.inject.Qualifier; import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -79,20 +76,14 @@ public @interface Cdi Class<?>[] recursivePackages() default {}; /** - * @return if the automatic scanning must be disabled. + * @return SeContainer properties. */ - boolean disableDiscovery() default false; + Property[] properties() default {}; /** - * When present on a test method parameter, it will <em>not</em> be attempted to be resolved with a CDI bean. + * @return if the automatic scanning must be disabled. */ - @Qualifier - @Target(PARAMETER) - @Retention(RUNTIME) - @Documented - @interface DontInject - { - } + boolean disableDiscovery() default false; /** * @return an array of callback to call before the container starts. @@ -113,4 +104,13 @@ public @interface Cdi interface OnStart extends Supplier<Closeable> { } + + /** + * A property set in SeContainer (as String). + */ + @interface Property + { + String name(); + String value(); + } } diff --git a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/CdiMethodParameters.java b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/CdiMethodParameters.java new file mode 100644 index 0000000..89e07f1 --- /dev/null +++ b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/CdiMethodParameters.java @@ -0,0 +1,39 @@ +/* + * 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.openwebbeans.junit5; + +import org.apache.openwebbeans.junit5.internal.CdiParametersResolverExtension; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Enables method parameter injection using CDI lookups. + */ +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@ExtendWith(CdiParametersResolverExtension.class) +public @interface CdiMethodParameters +{ +} diff --git a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/SkipInject.java b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/SkipInject.java new file mode 100644 index 0000000..b9b1af3 --- /dev/null +++ b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/SkipInject.java @@ -0,0 +1,36 @@ +/* + * 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.openwebbeans.junit5; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Qualifier you can put in method parameters to prevent the injection (adding an unresolved qualifier). + */ +@Qualifier +@Target(PARAMETER) +@Retention(RUNTIME) +public @interface SkipInject +{ +} diff --git a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiExtension.java b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiExtension.java index 0dfe9ed..c5852c0 100644 --- a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiExtension.java +++ b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiExtension.java @@ -24,33 +24,24 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; 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 org.junit.platform.commons.util.AnnotationUtils; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.se.SeContainer; import javax.enterprise.inject.se.SeContainerInitializer; import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionTarget; import java.io.Closeable; import java.io.IOException; -import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Parameter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Objects; -import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; // todo: enhance the setup to be thread safe? see Meecrowave ClassLoaderLock class and friends -public class CdiExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver +public class CdiExtension extends CdiParametersResolverExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback { private static SeContainer reusableContainer; @@ -93,6 +84,7 @@ public class CdiExtension implements BeforeAllCallback, AfterAllCallback, Before Stream.of(config.packages()).map(Class::getPackage).toArray(Package[]::new)); initializer.addPackages(true, Stream.of(config.recursivePackages()).map(Class::getPackage).toArray(Package[]::new)); + Stream.of(config.properties()).forEach(property -> initializer.addProperty(property.name(), property.value())); onStop = Stream.of(config.onStarts()) .map(it -> { @@ -110,7 +102,6 @@ public class CdiExtension implements BeforeAllCallback, AfterAllCallback, Before } }) .peek(Supplier::get) - .filter(Objects::nonNull) .toArray(Closeable[]::new); SeContainer container = initializer.initialize(); if (reusable) @@ -160,6 +151,7 @@ public class CdiExtension implements BeforeAllCallback, AfterAllCallback, Before @Override public void afterEach(final ExtensionContext extensionContext) { + super.afterEach(extensionContext); if (!creationalContexts.isEmpty()) { creationalContexts.forEach(CreationalContext::release); @@ -194,53 +186,4 @@ public class CdiExtension implements BeforeAllCallback, AfterAllCallback, Before return reusableContainer; } } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException - { - final SeContainer container = getContainer(); - if (container == null) - { - return false; - } - - Bean<?> bean = resolveParameterBean(container, parameterContext, extensionContext); - return bean != null; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException - { - final SeContainer container = getContainer(); - if (container == null) - { - return false; - } - - Bean<?> bean = resolveParameterBean(container, parameterContext, extensionContext); - BeanManager beanManager = container.getBeanManager(); - CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean); - creationalContexts.add(creationalContext); - return beanManager.getReference(bean, parameterContext.getParameter().getType(), creationalContext); - } - - private Bean<?> resolveParameterBean(SeContainer container, ParameterContext parameterContext, ExtensionContext extensionContext) - { - BeanManager beanManager = container.getBeanManager(); - Set<Bean<?>> beans = beanManager.getBeans( - parameterContext.getParameter().getType(), - getQualifiers(parameterContext.getParameter())); - return beanManager.resolve(beans); - } - - private Annotation[] getQualifiers(Parameter parameter) - { - final BeanManager beanManager = getContainer().getBeanManager(); - return Arrays.stream(parameter.getAnnotations()) - .filter(annotation -> beanManager.isQualifier(annotation.annotationType())) - .toArray(Annotation[]::new); - } - } diff --git a/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiParametersResolverExtension.java b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiParametersResolverExtension.java new file mode 100644 index 0000000..5bd9c62 --- /dev/null +++ b/webbeans-junit5/src/main/java/org/apache/openwebbeans/junit5/internal/CdiParametersResolverExtension.java @@ -0,0 +1,129 @@ +/* + * 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.openwebbeans.junit5.internal; + +import org.junit.jupiter.api.extension.AfterEachCallback; +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 javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.CDI; +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class CdiParametersResolverExtension implements ParameterResolver, AfterEachCallback +{ + private static final ExtensionContext.Namespace NAMESPACE = + ExtensionContext.Namespace.create(CdiParametersResolverExtension.class.getName()); + + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException + { + final Instances instances = extensionContext.getStore(NAMESPACE).getOrComputeIfAbsent( + Instances.class, k -> new Instances(), Instances.class); + if (instances != null && instances.instances != null && + instances.instances.containsKey(parameterContext.getParameter())) + { + return false; // already handled + } + try + { + final Parameter parameter = parameterContext.getParameter(); + final BeanManager bm = CDI.current().getBeanManager(); + final Bean<?> bean = resolveParameterBean(bm, parameter); + if (bean == null) + { + return false; + } + final CreationalContext<Object> creationalContext = bm.createCreationalContext(null); + final Object instance = bm.getReference(bean, parameter.getType(), creationalContext); + if (instances.instances == null) + { + instances.instances = new HashMap<>(); + } + instances.instances.put(parameter, new Instance(instance, creationalContext)); + return true; + } + catch (final IllegalStateException ise) // no cdi container + { + return false; + } + } + + @Override + public void afterEach(final ExtensionContext context) + { + final Instances instances = context.getStore(NAMESPACE).get(Instances.class, Instances.class); + if (instances != null && instances.instances != null) + { + instances.instances.values().stream().map(i -> i.creationalContext).forEach(CreationalContext::release); + } + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException + { + final Instances instances = extensionContext.getStore(NAMESPACE).get(Instances.class, Instances.class); + if (instances == null || instances.instances == null) + { + throw new ParameterResolutionException("No matching parameter: " + parameterContext.getParameter()); + } + return instances.instances.get(parameterContext.getParameter()).instance; + } + + private Bean<?> resolveParameterBean(final BeanManager beanManager, final Parameter parameter) + { + final Set<Bean<?>> beans = beanManager.getBeans(parameter.getType(), getQualifiers(beanManager, parameter)); + return beanManager.resolve(beans); + } + + private Annotation[] getQualifiers(final BeanManager beanManager, final Parameter parameter) + { + return Arrays.stream(parameter.getAnnotations()) + .filter(annotation -> beanManager.isQualifier(annotation.annotationType())) + .toArray(Annotation[]::new); + } + + private static class Instance + { + private final Object instance; + private final CreationalContext<?> creationalContext; + + private Instance(final Object instance, final CreationalContext<?> creationalContext) + { + this.instance = instance; + this.creationalContext = creationalContext; + } + } + + private static class Instances + { + private Map<Parameter, Instance> instances; + } +} diff --git a/webbeans-junit5/src/test/java/org/apache/openwebbeans/junit5/CdiParameterResolversTest.java b/webbeans-junit5/src/test/java/org/apache/openwebbeans/junit5/CdiParameterResolversTest.java new file mode 100644 index 0000000..73e2309 --- /dev/null +++ b/webbeans-junit5/src/test/java/org/apache/openwebbeans/junit5/CdiParameterResolversTest.java @@ -0,0 +1,96 @@ +/* + * 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.openwebbeans.junit5; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +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 javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.CDI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Cdi(classes = CdiParameterResolversTest.SomeBean.class, disableDiscovery = true) +class CdiParameterResolversTest +{ + @Test + void noParam() + { + assertNotNull(CDI.current().getBeanManager()); + } + + @Test + @CdiMethodParameters + void cdiParam(final SomeBean someBean) + { + assertNotNull(someBean); + assertEquals("yes", someBean.ok()); + assertTrue(someBean.getClass().getName().contains("$$Owb")); // it is cdi proxy + } + + @Test + @CdiMethodParameters + @ExtendWith(CustomParamResolver.class) + void mixedParams(final SomeBean cdi, @SkipInject final SomeBean notCdi) + { + assertNotNull(cdi); + assertEquals("yes", cdi.ok()); + assertEquals("custom", notCdi.ok()); + } + + @ApplicationScoped + public static class SomeBean + { + public String ok() + { + return "yes"; + } + } + + public static class CustomParamResolver implements ParameterResolver + { + + @Override + public boolean supportsParameter(final ParameterContext parameterContext, + final ExtensionContext extensionContext) throws ParameterResolutionException + { + return parameterContext.getIndex() == 1; + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException + { + return new SomeBean() + { + @Override + public String ok() + { + return "custom"; + } + }; + } + } +}