diff --git a/impl/src/main/java/org/jboss/weld/bean/builtin/AbstractFacade.java b/impl/src/main/java/org/jboss/weld/bean/builtin/AbstractFacade.java index e80c34009e..7bfbd11ec7 100644 --- a/impl/src/main/java/org/jboss/weld/bean/builtin/AbstractFacade.java +++ b/impl/src/main/java/org/jboss/weld/bean/builtin/AbstractFacade.java @@ -20,6 +20,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.Set; import jakarta.enterprise.context.spi.CreationalContext; @@ -40,12 +41,23 @@ public abstract class AbstractFacade { protected static Type getFacadeType(InjectionPoint injectionPoint) { Type genericType = injectionPoint.getType(); if (genericType instanceof ParameterizedType) { - return ((ParameterizedType) genericType).getActualTypeArguments()[0]; + Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; + if (typeArgument instanceof WildcardType) { + return getWildcardBound((WildcardType) typeArgument); + } + return typeArgument; } else { throw new IllegalStateException(BeanLogger.LOG.typeParameterMustBeConcrete(injectionPoint)); } } + private static Type getWildcardBound(WildcardType wildcard) { + if (wildcard.getLowerBounds().length > 0) { + return wildcard.getLowerBounds()[0]; + } + return wildcard.getUpperBounds()[0]; + } + private final BeanManagerImpl beanManager; private final InjectionPoint injectionPoint; // The CreationalContext used to create the facade which was injected. diff --git a/impl/src/main/java/org/jboss/weld/bootstrap/Validator.java b/impl/src/main/java/org/jboss/weld/bootstrap/Validator.java index e00c1b2504..234991a6c8 100644 --- a/impl/src/main/java/org/jboss/weld/bootstrap/Validator.java +++ b/impl/src/main/java/org/jboss/weld/bootstrap/Validator.java @@ -886,8 +886,11 @@ private static void checkFacadeInjectionPoint(InjectionPoint injectionPoint, Cla Formats.formatAsStackTraceElement(injectionPoint)); } if (parameterizedType.getActualTypeArguments()[0] instanceof WildcardType) { - throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint, - Formats.formatAsStackTraceElement(injectionPoint)); + WildcardType wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0]; + if (!isAllowedWildcard(wildcard, type)) { + throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint, + Formats.formatAsStackTraceElement(injectionPoint)); + } } } else if (type.equals(Event.class) && parameterizedType.getRawType().equals(Instance.class)) { // check for wildcard in Event injected via Instance -> @Inject Instance> @@ -895,13 +898,34 @@ private static void checkFacadeInjectionPoint(InjectionPoint injectionPoint, Cla if (instanceTypeArgument instanceof ParameterizedType && ((ParameterizedType) instanceTypeArgument).getRawType().equals(Event.class) && ((ParameterizedType) instanceTypeArgument).getActualTypeArguments()[0] instanceof WildcardType) { - throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint, - Formats.formatAsStackTraceElement(injectionPoint)); + WildcardType nestedWildcard = (WildcardType) ((ParameterizedType) instanceTypeArgument) + .getActualTypeArguments()[0]; + if (!isAllowedWildcard(nestedWildcard, Event.class)) { + throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint, + Formats.formatAsStackTraceElement(injectionPoint)); + } } } } } + /** + * Event is contravariant so {@code Event} is allowed. + * Instance is covariant so {@code Instance} is allowed. + * Unbounded wildcards are rejected for both. + */ + private static boolean isAllowedWildcard(WildcardType wildcard, Class facadeType) { + if (facadeType.equals(Event.class)) { + return wildcard.getLowerBounds().length > 0; + } + if (facadeType.equals(Instance.class)) { + Type[] upperBounds = wildcard.getUpperBounds(); + return wildcard.getLowerBounds().length == 0 + && upperBounds.length > 0 && !Object.class.equals(upperBounds[0]); + } + return false; + } + public static void checkBeanMetadataInjectionPoint(Object bean, InjectionPoint ip, Type expectedTypeArgument) { if (!(ip.getType() instanceof ParameterizedType)) { throw ValidatorLogger.LOG.invalidBeanMetadataInjectionPointType(ip.getType(), ip, diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithContravariantEvent.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithContravariantEvent.java new file mode 100644 index 0000000000..d1759cedeb --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithContravariantEvent.java @@ -0,0 +1,16 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.inject.Inject; + +@ApplicationScoped +public class BeanWithContravariantEvent { + + @Inject + Event> lifecycleEvents; + + public void fireEvent(LifecycleEvent event) { + lifecycleEvents.fire(event); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithSimpleContravariantEvent.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithSimpleContravariantEvent.java new file mode 100644 index 0000000000..0f8d99aa8f --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/BeanWithSimpleContravariantEvent.java @@ -0,0 +1,16 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.inject.Inject; + +@ApplicationScoped +public class BeanWithSimpleContravariantEvent { + + @Inject + Event widgetEvents; + + public void fireWidget(Widget widget) { + widgetEvents.fire(widget); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/EventContravariantWildcardTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/EventContravariantWildcardTest.java new file mode 100644 index 0000000000..ec2421ad5d --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/EventContravariantWildcardTest.java @@ -0,0 +1,47 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +import static org.junit.Assert.assertTrue; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Verifies that {@code Event} injection points are valid and functional. + * {@code Event} is naturally contravariant — you fire subtypes into it — so a + * lower-bounded wildcard is a legitimate use case. + *

+ * This reproduces the scenario reported by Gavin King where Jakarta Data injects + * {@code Event>}. + * + * @see CDI #888 + */ +@RunWith(Arquillian.class) +public class EventContravariantWildcardTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(EventContravariantWildcardTest.class)) + .addClasses(BeanWithContravariantEvent.class, LifecycleEvent.class, LifecycleEventObserver.class, + BeanWithSimpleContravariantEvent.class, Widget.class, WidgetObserver.class); + } + + @Test + public void testParameterizedContravariantEventWildcard(BeanWithContravariantEvent bean, + LifecycleEventObserver observer) { + bean.fireEvent(new LifecycleEvent<>("test")); + assertTrue("LifecycleEvent should have been observed", observer.isObserved()); + } + + @Test + public void testSimpleContravariantEventWildcard(BeanWithSimpleContravariantEvent bean, + WidgetObserver observer) { + bean.fireWidget(new Widget("test")); + assertTrue("Widget event should have been observed", observer.isObserved()); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEvent.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEvent.java new file mode 100644 index 0000000000..28ba4ced46 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEvent.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +public class LifecycleEvent { + + private final T payload; + + public LifecycleEvent(T payload) { + this.payload = payload; + } + + public T getPayload() { + return payload; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEventObserver.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEventObserver.java new file mode 100644 index 0000000000..a2bf518eef --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/LifecycleEventObserver.java @@ -0,0 +1,18 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; + +@ApplicationScoped +public class LifecycleEventObserver { + + private boolean observed = false; + + public void onLifecycleEvent(@Observes LifecycleEvent event) { + observed = true; + } + + public boolean isObserved() { + return observed; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/Widget.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/Widget.java new file mode 100644 index 0000000000..f5aaa5fe1b --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/Widget.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +public class Widget { + + private final String name; + + public Widget(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/WidgetObserver.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/WidgetObserver.java new file mode 100644 index 0000000000..3c9c3bf600 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/contravariant/WidgetObserver.java @@ -0,0 +1,18 @@ +package org.jboss.weld.tests.event.wildcard.contravariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; + +@ApplicationScoped +public class WidgetObserver { + + private boolean observed = false; + + public void onWidget(@Observes Widget event) { + observed = true; + } + + public boolean isObserved() { + return observed; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/BeanWithCovariantEvent.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/BeanWithCovariantEvent.java new file mode 100644 index 0000000000..05cbdfd967 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/BeanWithCovariantEvent.java @@ -0,0 +1,12 @@ +package org.jboss.weld.tests.event.wildcard.covariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.inject.Inject; + +@ApplicationScoped +public class BeanWithCovariantEvent { + + @Inject + Event covariantEvent; +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/EventCovariantWildcardTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/EventCovariantWildcardTest.java new file mode 100644 index 0000000000..784363b585 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/EventCovariantWildcardTest.java @@ -0,0 +1,35 @@ +package org.jboss.weld.tests.event.wildcard.covariant; + +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Verifies that {@code Event} injection points are rejected. + * Covariant wildcards on Event are useless because you cannot call + * {@code fire()} on them. + * + * @see CDI #888 + */ +@RunWith(Arquillian.class) +public class EventCovariantWildcardTest { + + @Deployment + @ShouldThrowException(DefinitionException.class) + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(EventCovariantWildcardTest.class)) + .addClasses(BeanWithCovariantEvent.class, Widget.class); + } + + @Test + public void testCovariantEventWildcardRejected() { + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/Widget.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/Widget.java new file mode 100644 index 0000000000..2505d19bfd --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/event/wildcard/covariant/Widget.java @@ -0,0 +1,4 @@ +package org.jboss.weld.tests.event.wildcard.covariant; + +public class Widget { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/BeanWithContravariantInstance.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/BeanWithContravariantInstance.java new file mode 100644 index 0000000000..1de2b0a037 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/BeanWithContravariantInstance.java @@ -0,0 +1,12 @@ +package org.jboss.weld.tests.instance.wildcard.contravariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +@ApplicationScoped +public class BeanWithContravariantInstance { + + @Inject + Instance contravariantInstance; +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/InstanceContravariantWildcardTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/InstanceContravariantWildcardTest.java new file mode 100644 index 0000000000..3f3bf4cc2f --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/InstanceContravariantWildcardTest.java @@ -0,0 +1,36 @@ +package org.jboss.weld.tests.instance.wildcard.contravariant; + +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Verifies that {@code Instance} injection points are rejected. + * Contravariant wildcards on Instance are useless because Instance is + * naturally covariant. + * + * @see CDI #888 + */ +@RunWith(Arquillian.class) +public class InstanceContravariantWildcardTest { + + @Deployment + @ShouldThrowException(DefinitionException.class) + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, + Utils.getDeploymentNameAsHash(InstanceContravariantWildcardTest.class)) + .addClasses(BeanWithContravariantInstance.class, Widget.class); + } + + @Test + public void testContravariantInstanceWildcardRejected() { + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/Widget.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/Widget.java new file mode 100644 index 0000000000..47fdef3f3a --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/contravariant/Widget.java @@ -0,0 +1,4 @@ +package org.jboss.weld.tests.instance.wildcard.contravariant; + +public class Widget { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/BeanWithCovariantInstance.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/BeanWithCovariantInstance.java new file mode 100644 index 0000000000..4bb5ead9b3 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/BeanWithCovariantInstance.java @@ -0,0 +1,20 @@ +package org.jboss.weld.tests.instance.wildcard.covariant; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +@ApplicationScoped +public class BeanWithCovariantInstance { + + @Inject + Instance covariantInstance; + + public boolean isResolvable() { + return covariantInstance.isResolvable(); + } + + public Widget get() { + return covariantInstance.get(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/InstanceCovariantWildcardTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/InstanceCovariantWildcardTest.java new file mode 100644 index 0000000000..8a55ecceb1 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/InstanceCovariantWildcardTest.java @@ -0,0 +1,37 @@ +package org.jboss.weld.tests.instance.wildcard.covariant; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Verifies that {@code Instance} injection points are valid and + * functional. Instance is naturally covariant so an upper-bounded wildcard is + * a legitimate use case. + * + * @see CDI #888 + */ +@RunWith(Arquillian.class) +public class InstanceCovariantWildcardTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InstanceCovariantWildcardTest.class)) + .addClasses(BeanWithCovariantInstance.class, Widget.class); + } + + @Test + public void testCovariantInstanceWildcardDeploys(BeanWithCovariantInstance bean) { + assertTrue("Instance should be resolvable", bean.isResolvable()); + Widget widget = bean.get(); + assertNotNull("Instance.get() should return a Widget", widget); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/Widget.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/Widget.java new file mode 100644 index 0000000000..7d82c4f132 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/instance/wildcard/covariant/Widget.java @@ -0,0 +1,17 @@ +package org.jboss.weld.tests.instance.wildcard.covariant; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class Widget { + + private final String name; + + public Widget() { + this.name = "default"; + } + + public String getName() { + return name; + } +}