diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java index 79ca737402578..680506aa09dad 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java @@ -7,9 +7,9 @@ import java.util.List; import java.util.stream.Collectors; +import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.spi.DeploymentException; import jakarta.inject.Inject; -import jakarta.inject.Singleton; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -36,7 +36,7 @@ public void testFailure() { fail(); } - @Singleton + @Dependent static class Foo { @Inject diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java index b2c95b7836c4b..698106c7c2bbb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java @@ -10,6 +10,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import jakarta.enterprise.context.Dependent; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; @@ -137,6 +138,7 @@ public interface HasMessage { } @Provider + @Dependent public static class HasMessageMessageBodyWriter implements ServerMessageBodyWriter { @Override diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 83ccfac1497a4..0dcd87b2927c7 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1422,7 +1422,10 @@ private List findInterceptors(List injectio // Skip vetoed interceptors continue; } - interceptors.add(Interceptors.createInterceptor(interceptorClass, this, injectionPointTransformer)); + InterceptorInfo interceptor = Interceptors.createInterceptor(interceptorClass, this, injectionPointTransformer); + if (interceptor != null) { + interceptors.add(interceptor); + } } if (LOGGER.isTraceEnabled()) { for (InterceptorInfo interceptor : interceptors) { @@ -1448,7 +1451,10 @@ private List findDecorators(List injectionPoi // Skip vetoed decorators continue; } - decorators.add(Decorators.createDecorator(decoratorClass, this, injectionPointTransformer)); + DecoratorInfo decorator = Decorators.createDecorator(decoratorClass, this, injectionPointTransformer); + if (decorator != null) { + decorators.add(decorator); + } } if (LOGGER.isTraceEnabled()) { for (DecoratorInfo decorator : decorators) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 621be618c9787..5e35c2d14530d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -1276,6 +1276,12 @@ BeanInfo create() { } else { scope = scopes.get(0); } + if (scope != null // `null` is just like `@Dependent` + && !BuiltinScope.DEPENDENT.is(scope) + && !beanClass.typeParameters().isEmpty()) { + throw new DefinitionException( + "Declaring class of a managed bean is generic, its scope must be @Dependent: " + beanClass); + } if (!isAlternative) { isAlternative = initStereotypeAlternative(stereotypes, beanDeployment); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java index 76e34376eef3a..00b60332dc142 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java @@ -108,6 +108,14 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> entry : beanDeployment.getTransitiveInterceptorBindings().entrySet()) { ResultHandle bindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -160,9 +168,9 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> removedBeans; private final Collection> observers; private final Collection contexts; + private final Set interceptorBindings; private final Map, Set> transitiveInterceptorBindings; private final Map> qualifierNonbindingMembers; private final Set qualifiers; public Components(Collection> beans, Collection> observers, Collection contexts, + Set interceptorBindings, Map, Set> transitiveInterceptorBindings, Supplier> removedBeans, Map> qualifierNonbindingMembers, Set qualifiers) { this.beans = beans; this.observers = observers; this.contexts = contexts; + this.interceptorBindings = interceptorBindings; this.transitiveInterceptorBindings = transitiveInterceptorBindings; this.removedBeans = removedBeans; this.qualifierNonbindingMembers = qualifierNonbindingMembers; @@ -42,6 +45,10 @@ public Collection getContexts() { return contexts; } + public Set getInterceptorBindings() { + return interceptorBindings; + } + public Map, Set> getTransitiveInterceptorBindings() { return transitiveInterceptorBindings; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java index 98af86b9f6a11..da3a4fcfacde6 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java @@ -1,7 +1,12 @@ package io.quarkus.arc; +import java.lang.annotation.Annotation; +import java.util.Set; + import jakarta.enterprise.inject.spi.Decorator; +import io.quarkus.arc.impl.Qualifiers; + /** * Quarkus representation of a decorator bean. * This interface extends the standard CDI {@link Decorator} interface. @@ -13,4 +18,8 @@ default Kind getKind() { return Kind.DECORATOR; } + @Override + default Set getDelegateQualifiers() { + return Qualifiers.DEFAULT_QUALIFIERS; + } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 2f6a85ecd8aa3..2310027778dc5 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -83,7 +84,6 @@ public class ArcContainerImpl implements ArcContainer { private final List> interceptors; private final List> decorators; private final List> observers; - private final Map, Set> transitiveInterceptorBindings; private final Contexts contexts; private final ComputingCache>> resolved; private final ComputingCache> beansById; @@ -93,6 +93,7 @@ public class ArcContainerImpl implements ArcContainer { final InstanceImpl instance; final Qualifiers registeredQualifiers; + final InterceptorBindings registeredInterceptorBindings; private volatile ExecutorService executorService; @@ -109,6 +110,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str List> interceptors = new ArrayList<>(); List> decorators = new ArrayList<>(); List> observers = new ArrayList<>(); + Set interceptorBindings = new HashSet<>(); Map, Set> transitiveInterceptorBindings = new HashMap<>(); Map> qualifierNonbindingMembers = new HashMap<>(); Set qualifiers = new HashSet<>(); @@ -132,6 +134,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str } removedBeans.add(c.getRemovedBeans()); observers.addAll(c.getObservers()); + interceptorBindings.addAll(c.getInterceptorBindings()); transitiveInterceptorBindings.putAll(c.getTransitiveInterceptorBindings()); qualifierNonbindingMembers.putAll(c.getQualifierNonbindingMembers()); qualifiers.addAll(c.getQualifiers()); @@ -140,7 +143,8 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str // register built-in beans addBuiltInBeans(beans); - interceptors.sort((i1, i2) -> Integer.compare(i2.getPriority(), i1.getPriority())); + interceptors.sort(Comparator.comparingInt(InjectableInterceptor::getPriority)); + decorators.sort(Comparator.comparingInt(InjectableDecorator::getPriority)); resolved = new ComputingCache<>(this::resolve); beansById = new ComputingCache<>(this::findById); @@ -168,8 +172,8 @@ public List get() { return List.copyOf(removed); } }); - this.transitiveInterceptorBindings = Map.copyOf(transitiveInterceptorBindings); this.registeredQualifiers = new Qualifiers(qualifiers, qualifierNonbindingMembers); + this.registeredInterceptorBindings = new InterceptorBindings(interceptorBindings, transitiveInterceptorBindings); Contexts.Builder contextsBuilder = new Contexts.Builder( new RequestContext(this.currentContextFactory.create(RequestScoped.class), @@ -177,7 +181,8 @@ public List get() { notifierOrNull(Set.of(BeforeDestroyed.Literal.REQUEST, Any.Literal.INSTANCE)), notifierOrNull(Set.of(Destroyed.Literal.REQUEST, Any.Literal.INSTANCE))), new ApplicationContext(), - new SingletonContext()); + new SingletonContext(), + new DependentContext()); // Add custom contexts for (Components c : components) { @@ -540,10 +545,6 @@ Set> getBeans(String name) { return new HashSet<>(getMatchingBeans(name)); } - Map, Set> getTransitiveInterceptorBindings() { - return transitiveInterceptorBindings; - } - boolean isScope(Class annotationType) { if (annotationType.isAnnotationPresent(Scope.class) || annotationType.isAnnotationPresent(NormalScope.class)) { return true; @@ -588,6 +589,11 @@ private InjectableBean findById(String identifier) { return interceptorBean; } } + for (InjectableDecorator decoratorBean : decorators) { + if (decoratorBean.getIdentifier().equals(identifier)) { + return decoratorBean; + } + } return null; } @@ -794,11 +800,12 @@ List> resolveInterceptors(InterceptionType type, Annotation... in if (interceptorBindings.length == 0) { throw new IllegalArgumentException("No interceptor bindings"); } + registeredInterceptorBindings.verify(interceptorBindings); List> interceptors = new ArrayList<>(); List bindings = new ArrayList<>(); for (Annotation binding : interceptorBindings) { bindings.add(binding); - Set transitive = transitiveInterceptorBindings.get(binding.annotationType()); + Set transitive = registeredInterceptorBindings.getTransitive(binding.annotationType()); if (transitive != null) { bindings.addAll(transitive); } @@ -818,10 +825,14 @@ List> resolveDecorators(Set types, Annotation... qualifiers) if (Objects.requireNonNull(types).isEmpty()) { throw new IllegalArgumentException("The set of bean types must not be empty"); } + if (qualifiers == null || qualifiers.length == 0) { + qualifiers = new Annotation[] { Default.Literal.INSTANCE }; + } else { + registeredQualifiers.verify(qualifiers); + } List> decorators = new ArrayList<>(); for (InjectableDecorator decorator : this.decorators) { - if (decoratorMatches(types, Set.of(qualifiers), decorator.getDelegateType(), - decorator.getDelegateQualifiers().toArray(new Annotation[] {}))) { + if (decoratorMatches(decorator.getDelegateType(), decorator.getDelegateQualifiers(), types, Set.of(qualifiers))) { decorators.add(decorator); } } @@ -866,12 +877,12 @@ private boolean matches(Set beanTypes, Set beanQualifiers, Typ return registeredQualifiers.hasQualifiers(beanQualifiers, qualifiers); } - private boolean decoratorMatches(Set beanTypes, Set beanQualifiers, Type delegateType, - Annotation... delegateQualifiers) { - if (!DelegateInjectionPointAssignabilityRules.instance().matches(delegateType, beanTypes)) { + private boolean decoratorMatches(Type delegateType, Set delegateQualifiers, Set requiredTypes, + Set requiredQualifiers) { + if (!DelegateInjectionPointAssignabilityRules.instance().matches(delegateType, requiredTypes)) { return false; } - return registeredQualifiers.hasQualifiers(beanQualifiers, delegateQualifiers); + return registeredQualifiers.hasQualifiers(delegateQualifiers, requiredQualifiers.toArray(new Annotation[0])); } static ArcContainerImpl unwrap(ArcContainer container) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java index 2386de2352730..aa47c4be52cbe 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java @@ -166,7 +166,7 @@ public boolean isQualifier(Class annotationType) { @Override public boolean isInterceptorBinding(Class annotationType) { return annotationType.isAnnotationPresent(InterceptorBinding.class) - || ArcContainerImpl.instance().getTransitiveInterceptorBindings().containsKey(annotationType); + || ArcContainerImpl.instance().registeredInterceptorBindings.isRegistered(annotationType); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java index af2567c577b9c..5858b982dcc87 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java @@ -10,6 +10,7 @@ import java.util.Set; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Singleton; @@ -27,10 +28,12 @@ class Contexts { final ManagedContext requestContext; final InjectableContext applicationContext; final InjectableContext singletonContext; + final InjectableContext dependentContext; // Used to optimize the getContexts(Class) private final List applicationContextSingleton; private final List singletonContextSingleton; + private final List dependentContextSingleton; private final List requestContextSingleton; // Lazily computed list of contexts for a scope @@ -39,14 +42,16 @@ class Contexts { // Precomputed values final Set> scopes; - Contexts(ManagedContext requestContext, InjectableContext applicationContext, - InjectableContext singletonContext, Map, List> contexts) { + Contexts(ManagedContext requestContext, InjectableContext applicationContext, InjectableContext singletonContext, + InjectableContext dependentContext, Map, List> contexts) { this.requestContext = requestContext; this.applicationContext = applicationContext; this.singletonContext = singletonContext; + this.dependentContext = dependentContext; this.applicationContextSingleton = Collections.singletonList(applicationContext); this.singletonContextSingleton = Collections.singletonList(singletonContext); + this.dependentContextSingleton = Collections.singletonList(dependentContext); List requestContexts = contexts.get(RequestScoped.class); this.requestContextSingleton = requestContexts != null ? requestContexts : Collections.singletonList(requestContext); @@ -77,21 +82,24 @@ protected List computeValue(Class type) { Set> all = new HashSet<>(contexts.keySet()); all.add(ApplicationScoped.class); all.add(Singleton.class); + all.add(Dependent.class); all.add(RequestScoped.class); this.scopes = Set.copyOf(all); } else { // No custom context is registered this.unoptimizedContexts = null; - this.scopes = Set.of(ApplicationScoped.class, Singleton.class, RequestScoped.class); + this.scopes = Set.of(ApplicationScoped.class, Singleton.class, Dependent.class, RequestScoped.class); } } InjectableContext getActiveContext(Class scopeType) { - // Application/Singleton context is always active and it's not possible to register a custom context for these scopes + // Application/Singleton/Dependent context is always active and it's not possible to register a custom context for these scopes if (ApplicationScoped.class.equals(scopeType)) { return applicationContext; } else if (Singleton.class.equals(scopeType)) { return singletonContext; + } else if (Dependent.class.equals(scopeType)) { + return dependentContext; } List contextsForScope = getContexts(scopeType); InjectableContext selected = null; @@ -99,7 +107,7 @@ InjectableContext getActiveContext(Class scopeType) { if (context.isActive()) { if (selected != null) { throw new IllegalArgumentException( - "More than one context object for the given scope: " + selected + " " + context); + "More than one active context object for the given scope: " + selected + " " + context); } selected = context; } @@ -108,13 +116,15 @@ InjectableContext getActiveContext(Class scopeType) { } List getContexts(Class scopeType) { - // Optimize for buil-in normal scopes - this method is used internally during client proxy invocation + // Optimize for built-in scopes - this method is used internally during client proxy invocation if (ApplicationScoped.class.equals(scopeType)) { return applicationContextSingleton; } else if (RequestScoped.class.equals(scopeType)) { return requestContextSingleton; } else if (Singleton.class.equals(scopeType)) { return singletonContextSingleton; + } else if (Dependent.class.equals(scopeType)) { + return dependentContextSingleton; } return unoptimizedContexts != null ? unoptimizedContexts.get(scopeType) : Collections.emptyList(); } @@ -124,13 +134,15 @@ static class Builder { private final ManagedContext requestContext; private final InjectableContext applicationContext; private final InjectableContext singletonContext; + private final InjectableContext dependentContext; private final Map, List> contexts = new HashMap<>(); public Builder(ManagedContext requestContext, InjectableContext applicationContext, - InjectableContext singletonContext) { + InjectableContext singletonContext, InjectableContext dependentContext) { this.requestContext = requestContext; this.applicationContext = applicationContext; this.singletonContext = singletonContext; + this.dependentContext = dependentContext; } Builder putContext(InjectableContext context) { @@ -151,7 +163,7 @@ Contexts build() { // If a custom request context is registered then add the built-in context as well putContext(requestContext); } - return new Contexts(requestContext, applicationContext, singletonContext, contexts); + return new Contexts(requestContext, applicationContext, singletonContext, dependentContext, contexts); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java new file mode 100644 index 0000000000000..cf6919e188e2c --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java @@ -0,0 +1,64 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.util.Objects; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; + +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableContext; + +/** + * The built-in context for {@link jakarta.enterprise.context.Dependent}. + */ +class DependentContext implements InjectableContext { + @Override + public Class getScope() { + return Dependent.class; + } + + @Override + public T get(Contextual contextual, CreationalContext creationalContext) { + Objects.requireNonNull(contextual, "Contextual must not be null"); + + if (creationalContext == null) { + // there's never an "existing instance" of a dependent-scoped bean + return null; + } + + T instance = contextual.create(creationalContext); + if (creationalContext instanceof CreationalContextImpl) { + // we can remove this `if` and cast unconditionally after https://github.com/jakartaee/cdi-tck/pull/452 + CreationalContextImpl ccimpl = (CreationalContextImpl) creationalContext; + ccimpl.addDependentInstance((InjectableBean) contextual, instance, creationalContext); + } + return instance; + } + + @Override + public T get(Contextual contextual) { + return get(contextual, null); + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public void destroy(Contextual contextual) { + throw new UnsupportedOperationException(); + } + + @Override + public void destroy() { + throw new UnsupportedOperationException(); + } + + @Override + public ContextState getState() { + throw new UnsupportedOperationException(); + } +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java new file mode 100644 index 0000000000000..0abebd30ac73b --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java @@ -0,0 +1,70 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +public final class InterceptorBindings { + private final Set allInterceptorBindings; + private final Map, Set> transitiveInterceptorBindings; + + InterceptorBindings(Set interceptorBindings, + Map, Set> transitiveInterceptorBindings) { + this.allInterceptorBindings = interceptorBindings; + this.transitiveInterceptorBindings = transitiveInterceptorBindings; + } + + boolean isRegistered(Class annotationType) { + return allInterceptorBindings.contains(annotationType.getName()); + } + + Set getTransitive(Class interceptorBinding) { + return transitiveInterceptorBindings.get(interceptorBinding); + } + + void verify(Annotation[] interceptorBindings) { + if (interceptorBindings.length == 0) { + return; + } + if (interceptorBindings.length == 1) { + verifyInterceptorBinding(interceptorBindings[0].annotationType()); + } else { + Map, Integer> timesQualifierWasSeen = new HashMap<>(); + for (Annotation interceptorBinding : interceptorBindings) { + verifyInterceptorBinding(interceptorBinding.annotationType()); + timesQualifierWasSeen.compute(interceptorBinding.annotationType(), TimesSeenBiFunction.INSTANCE); + } + checkInterceptorBindingsForDuplicates(timesQualifierWasSeen); + } + } + + // in various cases, specification requires to check interceptor bindings for duplicates and throw IAE + private static void checkInterceptorBindingsForDuplicates(Map, Integer> timesSeen) { + timesSeen.forEach(InterceptorBindings::checkInterceptorBindingsForDuplicates); + } + + private static void checkInterceptorBindingsForDuplicates(Class annClass, Integer timesSeen) { + if (timesSeen > 1 && !annClass.isAnnotationPresent(Repeatable.class)) { + throw new IllegalArgumentException("Interceptor binding " + annClass + + " was used repeatedly but is not @Repeatable"); + } + } + + private void verifyInterceptorBinding(Class annotationType) { + if (!allInterceptorBindings.contains(annotationType.getName())) { + throw new IllegalArgumentException("Annotation is not a registered interceptor binding: " + annotationType); + } + } + + private static class TimesSeenBiFunction implements BiFunction, Integer, Integer> { + private static final TimesSeenBiFunction INSTANCE = new TimesSeenBiFunction(); + + @Override + public Integer apply(Class k, Integer v) { + return v == null ? 1 : v + 1; + } + } +} diff --git a/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java b/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java index ee91e4c427381..d87675bfa11ae 100644 --- a/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java +++ b/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java @@ -1,13 +1,10 @@ package io.quarkus.arc.tck.porting; -import java.lang.annotation.Annotation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.spi.Context; -import jakarta.enterprise.context.spi.Contextual; -import jakarta.enterprise.context.spi.CreationalContext; import org.jboss.cdi.tck.spi.Contexts; @@ -40,30 +37,7 @@ public Context getRequestContext() { @Override public Context getDependentContext() { - // ArC doesn't have a context object for the dependent context, let's fake it here for now - // TODO we'll likely have to implement this in ArC properly - - return new Context() { - @Override - public Class getScope() { - return Dependent.class; - } - - @Override - public T get(Contextual contextual, CreationalContext creationalContext) { - throw new UnsupportedOperationException(); - } - - @Override - public T get(Contextual contextual) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isActive() { - return true; - } - }; + return Arc.container().getActiveContext(Dependent.class); } @Override diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index d1ce5d1d664fd..f7630e55ef81d 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -80,6 +80,7 @@ + @@ -138,11 +139,6 @@ - - - - - @@ -167,10 +163,7 @@ - - - - + @@ -192,26 +185,9 @@ - - - - - - - - - - - - - - - - - - + @@ -219,11 +195,6 @@ - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java new file mode 100644 index 0000000000000..3260007e17da4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java @@ -0,0 +1,33 @@ +package io.quarkus.arc.test.bean.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NonDependentGenericBeanTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(IllegalBean.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Declaring class of a managed bean is generic, its scope must be @Dependent")); + } + + @ApplicationScoped + static class IllegalBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java index 99536e2a7a078..2d3d096c18ad9 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java @@ -29,6 +29,8 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.RequestScoped; @@ -46,6 +48,7 @@ import jakarta.enterprise.inject.spi.ObserverMethod; import jakarta.enterprise.util.AnnotationLiteral; import jakarta.enterprise.util.Nonbinding; +import jakarta.enterprise.util.TypeLiteral; import jakarta.inject.Inject; import jakarta.inject.Qualifier; import jakarta.inject.Singleton; @@ -60,6 +63,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ManagedContext; +import io.quarkus.arc.processor.InterceptorBindingRegistrar; import io.quarkus.arc.processor.QualifierRegistrar; import io.quarkus.arc.test.ArcTestContainer; @@ -68,15 +72,22 @@ public class BeanManagerTest { @RegisterExtension public ArcTestContainer container = new ArcTestContainer.Builder() .beanClasses(Legacy.class, AlternativeLegacy.class, Fool.class, LowFool.class, DummyInterceptor.class, - DummyBinding.class, + DummyBinding.class, UselessBinding.class, CustomBinding.class, LowPriorityInterceptor.class, WithInjectionPointMetadata.class, High.class, Low.class, Observers.class, - BeanWithCustomQualifier.class) + BeanWithCustomQualifier.class, + ToUpperCaseConverter.class, TrimConverterDecorator.class, RepeatDecorator.class) .qualifierRegistrars(new QualifierRegistrar() { @Override public Map> getAdditionalQualifiers() { return Map.of(DotName.createSimple(Low.class.getName()), Set.of()); } }) + .interceptorBindingRegistrars(new InterceptorBindingRegistrar() { + @Override + public List getAdditionalBindings() { + return List.of(InterceptorBinding.of(CustomBinding.class)); + } + }) .build(); @Test @@ -190,8 +201,35 @@ public void testResolveInterceptors() { interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new DummyBinding.Literal(false, true), new UselessBinding.Literal()); assertEquals(2, interceptors.size()); - assertEquals(DummyInterceptor.class, interceptors.get(0).getBeanClass()); - assertEquals(LowPriorityInterceptor.class, interceptors.get(1).getBeanClass()); + assertEquals(LowPriorityInterceptor.class, interceptors.get(0).getBeanClass()); + assertEquals(DummyInterceptor.class, interceptors.get(1).getBeanClass()); + + assertThrows(IllegalArgumentException.class, () -> { + // not an interceptor binding + beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new Default.Literal()); + }); + + assertThrows(IllegalArgumentException.class, () -> { + beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new DummyBinding.Literal(true, true), + new DummyBinding.Literal(false, false)); + }); + } + + @Test + public void testResolveDecorator() { + Set converterStringTypes = Set.of(new TypeLiteral>() { + }.getType()); + + BeanManager beanManager = Arc.container().beanManager(); + List> decorators; + decorators = beanManager.resolveDecorators(converterStringTypes); + assertEquals(2, decorators.size()); + assertEquals(RepeatDecorator.class, decorators.get(0).getBeanClass()); + assertEquals(TrimConverterDecorator.class, decorators.get(1).getBeanClass()); + + assertThrows(IllegalArgumentException.class, () -> { + beanManager.resolveDecorators(converterStringTypes, new Default.Literal(), new Default.Literal()); + }); } @Test @@ -207,6 +245,7 @@ public void testIsQualifier() { public void testIsInterceptorBinding() { BeanManager beanManager = Arc.container().beanManager(); assertTrue(beanManager.isInterceptorBinding(DummyBinding.class)); + assertTrue(beanManager.isInterceptorBinding(CustomBinding.class)); assertFalse(beanManager.isInterceptorBinding(Default.class)); } @@ -364,6 +403,12 @@ class Literal extends AnnotationLiteral implements UselessBindin } } + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + public @interface CustomBinding { + } + @DummyBinding(alpha = true, bravo = true) @Priority(10) @Interceptor @@ -394,4 +439,41 @@ static class WithInjectionPointMetadata { } + interface Converter { + T convert(T value); + } + + @Singleton + static class ToUpperCaseConverter implements Converter { + @Override + public String convert(String value) { + return value.toUpperCase(); + } + } + + @Decorator + @Priority(10) + static class TrimConverterDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + } + + @Decorator + @Priority(1) + static class RepeatDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value).repeat(2); + } + } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java new file mode 100644 index 0000000000000..31a3af6efed72 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.test.decorators.disabled; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DisabledDecoratorInStrictModeTest { + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, ToUpperCaseConverter.class, TrimConverterDecorator.class) + .strictCompatibility(true) + .build(); + + @Test + public void test() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals(" HOLA! ", converter.convert(" holA! ")); + } + + interface Converter { + T convert(T value); + } + + @Singleton + static class ToUpperCaseConverter implements Converter { + @Override + public String convert(String value) { + return value.toUpperCase(); + } + } + + @Decorator + // no @Priority, the decorator is disabled in strict mode + static class TrimConverterDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java index 790364cc5eb58..6e2fbc7bb2f6e 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,7 +22,7 @@ public void testBeans() { } - @ApplicationScoped + @Dependent static class Foo { protected String ping() { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java new file mode 100644 index 0000000000000..0a17e44130006 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.test.interceptors.disabled; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DisabledInterceptorInStrictModeTest { + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class) + .strictCompatibility(true) + .build(); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("pong", bean.ping()); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + String ping() { + return "pong"; + } + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + // no @Priority, the interceptor is disabled in strict mode + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java index 87c45c0f8bd3f..b996e91a8ce5e 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java @@ -25,19 +25,12 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ClientProxy; -import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.test.ArcTestContainer; public class PreDestroyInterceptorAndClientProxyTest { @RegisterExtension ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); - @Test - public void bagr() { - InstanceHandle handle = Arc.container().instance(MyBean.class); - handle.destroy(); - } - @Test public void test() { BeanManager beanManager = Arc.container().beanManager();