diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index fb870debc97a46..393ddcd55340ff 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -379,8 +379,8 @@ NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is re === Synthetic Beans Sometimes it is very useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class. -In CDI this could be achieved using `AfterBeanDiscovery.addBean()` methods. -In Quarkus we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. +In CDI, this could be achieved using `AfterBeanDiscovery.addBean()` methods. +In Quarkus, we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. [source,java] ---- @@ -398,6 +398,8 @@ BeanRegistrarBuildItem syntheticBean() { NOTE: The output of a `BeanConfigurator` is recorded as bytecode. Therefore there are some limitations in how a synthetic bean instance is created. See also `BeanConfigurator.creator()` methods. +TIP: You can easily filter all class-based beans via the convenient `BeanStream` returned from the `RegistrationContext.beans()` method. + If an extension needs to produce other build items during the "bean registration" phase it should use the `BeanRegistrationPhaseBuildItem` instead. The reason is that injected objects are only valid during a `@BuildStep` method invocation. @@ -511,7 +513,7 @@ BeanDeploymentValidatorBuildItem beanDeploymentValidator() { } ---- -NOTE: See also `io.quarkus.arc.processor.BuildExtension.Key` to discover the available metadata. +TIP: You can easily filter all registered beans via the convenient `BeanStream` returned from the `ValidationContext.beans()` method. If an extension needs to produce other build items during the "validation" phase it should use the `ValidationPhaseBuildItem` instead. The reason is that injected objects are only valid during a `@BuildStep` method invocation. @@ -574,13 +576,15 @@ The built-in keys located in `io.quarkus.arc.processor.BuildExtension.Key` are: * `ANNOTATION_STORE` ** Contains an `AnnotationStore` that keeps information about all `AnnotationTarget` annotations after application of annotation transformers * `INJECTION_POINTS` -** `List` containing all injection points +** `Collection` containing all injection points * `BEANS` -** `List` containing all beans +** `Collection` containing all beans +* `REMOVED_BEANS` +** `Collection` containing all the removed beans; see <> for more information * `OBSERVERS` -** `List` containing all observers +** `Collection` containing all observers * `SCOPES` -** `List` containing all scopes, including custom ones +** `Collection` containing all scopes, including custom ones * `QUALIFIERS` ** `Map` containing all qualifiers * `INTERCEPTOR_BINDINGS` @@ -600,7 +604,7 @@ Here is a summary of which extensions can access which metadata: * `InjectionPointsTransformer` ** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES` * `BeanRegistrar` -** Has access to all build metadata +** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES`, `BEANS` * `BeanDeploymentValidator` ** Has access to all build metadata diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java index c41bc5ef6ca7c4..4e4b90791b1306 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java @@ -127,12 +127,10 @@ void collectScheduledMethods( AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); // We need to collect all business methods annotated with @Scheduled first - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - collectScheduledMethods(config, beanArchives.getIndex(), annotationStore, bean, - bean.getTarget().get().asClass(), - scheduledBusinessMethods, validationPhase.getContext()); - } + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + collectScheduledMethods(config, beanArchives.getIndex(), annotationStore, bean, + bean.getTarget().get().asClass(), + scheduledBusinessMethods, validationPhase.getContext()); } } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index afb25702018293..d5216ecf64fede 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; -import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.QuarkusConfig; @@ -192,10 +191,8 @@ public void transform(TransformationContext ctx) { void validateFaultToleranceAnnotations( ValidationPhaseBuildItem validationPhase, SmallryeFaultToleranceRecorder recorder) { List beanNames = new ArrayList<>(); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - beanNames.add(bean.getBeanClass().toString()); - } + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + beanNames.add(bean.getBeanClass().toString()); } recorder.validate(beanNames); } diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java index d38e136d647110..784fe29504477b 100644 --- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java +++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java @@ -338,39 +338,37 @@ void registerMetricsFromProducers( ValidationPhaseBuildItem validationPhase, BeanArchiveIndexBuildItem beanArchiveIndex) { IndexView index = beanArchiveIndex.getIndex(); - for (io.quarkus.arc.processor.BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isProducerField() || bean.isProducerMethod()) { - MetricType metricType = getMetricType(bean.getImplClazz()); - if (metricType != null) { - AnnotationTarget target = bean.getTarget().get(); - AnnotationInstance metricAnnotation = null; - String memberName = null; - if (bean.isProducerField()) { - FieldInfo field = target.asField(); - metricAnnotation = field.annotation(METRIC); - memberName = field.name(); - } - if (bean.isProducerMethod()) { - MethodInfo method = target.asMethod(); - metricAnnotation = method.annotation(METRIC); - memberName = method.name(); - } - if (metricAnnotation != null) { - String nameValue = metricAnnotation.valueWithDefault(index, "name").asString(); - boolean absolute = metricAnnotation.valueWithDefault(index, "absolute").asBoolean(); - String metricSimpleName = !nameValue.isEmpty() ? nameValue : memberName; - String declaringClassName = bean.getDeclaringBean().getImplClazz().name().toString(); - String metricsFinalName = absolute ? metricSimpleName - : MetricRegistry.name(declaringClassName, metricSimpleName); - recorder.registerMetricFromProducer( - bean.getIdentifier(), - metricType, - metricsFinalName, - metricAnnotation.valueWithDefault(index, "tags").asStringArray(), - metricAnnotation.valueWithDefault(index, "description").asString(), - metricAnnotation.valueWithDefault(index, "displayName").asString(), - metricAnnotation.valueWithDefault(index, "unit").asString()); - } + for (io.quarkus.arc.processor.BeanInfo bean : validationPhase.getContext().beans().producers()) { + MetricType metricType = getMetricType(bean.getImplClazz()); + if (metricType != null) { + AnnotationTarget target = bean.getTarget().get(); + AnnotationInstance metricAnnotation = null; + String memberName = null; + if (bean.isProducerField()) { + FieldInfo field = target.asField(); + metricAnnotation = field.annotation(METRIC); + memberName = field.name(); + } + if (bean.isProducerMethod()) { + MethodInfo method = target.asMethod(); + metricAnnotation = method.annotation(METRIC); + memberName = method.name(); + } + if (metricAnnotation != null) { + String nameValue = metricAnnotation.valueWithDefault(index, "name").asString(); + boolean absolute = metricAnnotation.valueWithDefault(index, "absolute").asBoolean(); + String metricSimpleName = !nameValue.isEmpty() ? nameValue : memberName; + String declaringClassName = bean.getDeclaringBean().getImplClazz().name().toString(); + String metricsFinalName = absolute ? metricSimpleName + : MetricRegistry.name(declaringClassName, metricSimpleName); + recorder.registerMetricFromProducer( + bean.getIdentifier(), + metricType, + metricsFinalName, + metricAnnotation.valueWithDefault(index, "tags").asStringArray(), + metricAnnotation.valueWithDefault(index, "description").asString(), + metricAnnotation.valueWithDefault(index, "displayName").asString(), + metricAnnotation.valueWithDefault(index, "unit").asString()); } } } diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index 7eebdbc3607860..7daedd19fee18c 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -118,27 +118,25 @@ void validateBeanDeployment( AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); // We need to collect all business methods annotated with @Incoming/@Outgoing first - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - // TODO: add support for inherited business methods - for (MethodInfo method : bean.getTarget().get().asClass().methods()) { - AnnotationInstance incoming = annotationStore.getAnnotation(method, - io.quarkus.smallrye.reactivemessaging.deployment.DotNames.INCOMING); - AnnotationInstance outgoing = annotationStore.getAnnotation(method, - io.quarkus.smallrye.reactivemessaging.deployment.DotNames.OUTGOING); - if (incoming != null || outgoing != null) { - if (incoming != null && incoming.value().asString().isEmpty()) { - validationPhase.getContext().addDeploymentProblem( - new DeploymentException("Empty @Incoming annotation on method " + method)); - } - if (outgoing != null && outgoing.value().asString().isEmpty()) { - validationPhase.getContext().addDeploymentProblem( - new DeploymentException("Empty @Outgoing annotation on method " + method)); - } - // TODO: validate method params and return type? - mediatorMethods.produce(new MediatorBuildItem(bean, method)); - LOGGER.debugf("Found mediator business method %s declared on %s", method, bean); + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + // TODO: add support for inherited business methods + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + AnnotationInstance incoming = annotationStore.getAnnotation(method, + io.quarkus.smallrye.reactivemessaging.deployment.DotNames.INCOMING); + AnnotationInstance outgoing = annotationStore.getAnnotation(method, + io.quarkus.smallrye.reactivemessaging.deployment.DotNames.OUTGOING); + if (incoming != null || outgoing != null) { + if (incoming != null && incoming.value().asString().isEmpty()) { + validationPhase.getContext().addDeploymentProblem( + new DeploymentException("Empty @Incoming annotation on method " + method)); } + if (outgoing != null && outgoing.value().asString().isEmpty()) { + validationPhase.getContext().addDeploymentProblem( + new DeploymentException("Empty @Outgoing annotation on method " + method)); + } + // TODO: validate method params and return type? + mediatorMethods.produce(new MediatorBuildItem(bean, method)); + LOGGER.debugf("Found mediator business method %s declared on %s", method, bean); } } } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index d102597adb9f27..8b98af3df949c8 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -104,7 +104,7 @@ void validateBeanDeployment( // Collect all business methods annotated with @Route and @RouteFilter AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { if (bean.isClassBean()) { // NOTE: inherited business methods are not taken into account ClassInfo beanClass = bean.getTarget().get().asClass(); diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index be4f0059373a88..b76021f5240298 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -108,23 +108,20 @@ void validateBeanDeployment( // We need to collect all business methods annotated with @MessageConsumer first AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - // TODO: inherited business methods? - for (MethodInfo method : bean.getTarget().get().asClass().methods()) { - AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); - if (consumeEvent != null) { - // Validate method params and return type - List params = method.parameters(); - if (params.size() != 1) { - throw new IllegalStateException(String.format( - "Event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s", - params, method, bean)); - } - messageConsumerBusinessMethods - .produce(new EventConsumerBusinessMethodItem(bean, method, consumeEvent)); - LOGGER.debugf("Found event consumer business method %s declared on %s", method, bean); + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); + if (consumeEvent != null) { + // Validate method params and return type + List params = method.parameters(); + if (params.size() != 1) { + throw new IllegalStateException(String.format( + "Event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s", + params, method, bean)); } + messageConsumerBusinessMethods + .produce(new EventConsumerBusinessMethodItem(bean, method, consumeEvent)); + LOGGER.debugf("Found event consumer business method %s declared on %s", method, bean); } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java index c78bbe1f34b6cc..0ae85aede3a131 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java @@ -59,7 +59,9 @@ public final class BeanConfigurator { private final Map params; - private boolean isDefaultBean; + private boolean defaultBean; + + private boolean removable; /** * @@ -77,6 +79,7 @@ public final class BeanConfigurator { this.scope = BuiltinScope.DEPENDENT.getInfo(); this.params = new HashMap<>(); this.name = null; + this.removable = true; } public BeanConfigurator param(String name, Class value) { @@ -152,7 +155,12 @@ public BeanConfigurator name(String name) { } public BeanConfigurator defaultBean() { - this.isDefaultBean = true; + this.defaultBean = true; + return this; + } + + public BeanConfigurator unremovable() { + this.removable = false; return this; } @@ -216,7 +224,7 @@ public void done() { .beanDeployment(beanDeployment).scope(scope).types(types) .qualifiers(qualifiers) .alternativePriority(alternativePriority).name(name).creator(creatorConsumer).destroyer(destroyerConsumer) - .params(params).defaultBean(isDefaultBean).build()); + .params(params).defaultBean(defaultBean).removable(removable).build()); } } 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 af993182d193ae..ac4d0e5475d703 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 @@ -241,6 +241,10 @@ void init(Consumer bytecodeTransformerConsumer) { if (bean.getName() != null) { continue test; } + // Unremovable synthetic beans + if (!bean.isRemovable()) { + continue test; + } // Custom exclusions for (Predicate exclusion : unusedExclusions) { if (exclusion.test(bean)) { @@ -294,6 +298,9 @@ void init(Consumer bytecodeTransformerConsumer) { } LOGGER.debugf("Removed %s unused beans in %s ms", removable.size(), System.currentTimeMillis() - removalStart); } + + buildContext.putInternal(BuildExtension.Key.REMOVED_BEANS.asString(), Collections.unmodifiableSet(removedBeans)); + LOGGER.debugf("Bean deployment initialized in %s ms", System.currentTimeMillis() - start); } @@ -836,6 +843,11 @@ public V put(Key key, V value) { return buildContext.put(key, value); } + @Override + public BeanStream beans() { + return new BeanStream(get(BuildExtension.Key.BEANS)); + } + }; for (BeanRegistrar registrar : beanRegistrars) { registrar.register(registrationContext); @@ -956,6 +968,16 @@ public List getDeploymentProblems() { return Collections.unmodifiableList(errors); } + @Override + public BeanStream beans() { + return new BeanStream(get(BuildExtension.Key.BEANS)); + } + + @Override + public BeanStream removedBeans() { + return new BeanStream(get(BuildExtension.Key.REMOVED_BEANS)); + } + } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java index 8b413eb53a77b7..310ccec0323340 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java @@ -38,6 +38,18 @@ interface ValidationContext extends BuildContext { List getDeploymentProblems(); + /** + * + * @return a new stream instance + */ + BeanStream beans(); + + /** + * + * @return a new stream instance + */ + BeanStream removedBeans(); + } public enum ValidationRule { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 8806d4c65fe6c6..90f7263aa70f9c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -69,9 +69,11 @@ public class BeanInfo implements InjectionTargetInfo { private final String name; - private final boolean isDefaultBean; + private final boolean defaultBean; - // Gizmo consumers are only used by synthetic beans + // Following fields are only used by synthetic beans + + private final boolean removable; private final Consumer creatorConsumer; @@ -87,7 +89,7 @@ public class BeanInfo implements InjectionTargetInfo { this(null, null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, null, null, - Collections.emptyMap()); + Collections.emptyMap(), true); } BeanInfo(ClassInfo implClazz, Type providerType, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, @@ -97,7 +99,7 @@ public class BeanInfo implements InjectionTargetInfo { List stereotypes, String name, boolean isDefaultBean, Consumer creatorConsumer, Consumer destroyerConsumer, - Map params) { + Map params, boolean isRemovable) { this.target = Optional.ofNullable(target); if (implClazz == null && target != null) { implClazz = initImplClazz(target, beanDeployment); @@ -126,9 +128,10 @@ public class BeanInfo implements InjectionTargetInfo { this.alternativePriority = alternativePriority; this.stereotypes = stereotypes; this.name = name; - this.isDefaultBean = isDefaultBean; + this.defaultBean = isDefaultBean; this.creatorConsumer = creatorConsumer; this.destroyerConsumer = destroyerConsumer; + this.removable = isRemovable; this.params = params; // Identifier must be unique for a specific deployment this.identifier = Hashes.sha1(toString()); @@ -178,6 +181,10 @@ public boolean isSynthetic() { return !target.isPresent(); } + public boolean isRemovable() { + return removable; + } + public DotName getBeanClass() { if (declaringBean != null) { return declaringBean.implClazz.name(); @@ -259,6 +266,10 @@ public boolean hasLifecycleInterceptors() { return !lifecycleInterceptors.isEmpty(); } + public boolean hasAroundInvokeInterceptors() { + return !interceptedMethods.isEmpty(); + } + boolean isSubclassRequired() { return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); } @@ -323,7 +334,7 @@ public String getName() { } public boolean isDefaultBean() { - return isDefaultBean; + return defaultBean; } Consumer getCreatorConsumer() { @@ -653,6 +664,8 @@ static class Builder { private Map params; + private boolean removable = true; + Builder() { injections = Collections.emptyList(); stereotypes = Collections.emptyList(); @@ -743,11 +756,15 @@ Builder params(Map params) { return this; } + Builder removable(boolean val) { + this.removable = val; + return this; + } + BeanInfo build() { return new BeanInfo(implClazz, providerType, target, beanDeployment, scope, types, qualifiers, injections, - declaringBean, - disposer, alternativePriority, - stereotypes, name, isDefaultBean, creatorConsumer, destroyerConsumer, params); + declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, creatorConsumer, + destroyerConsumer, params, removable); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java index 51fb9733806a30..47e755aec00647 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java @@ -34,6 +34,12 @@ default BeanConfigurator configure(Class beanClass) { return configure(DotName.createSimple(beanClass.getName())); } + /** + * + * @return a new stream instance + */ + BeanStream beans(); + } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java new file mode 100644 index 00000000000000..d2639b83645463 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java @@ -0,0 +1,156 @@ +package io.quarkus.arc.processor; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +/** + * Convenient {@link Stream} wrapper that can be used to filter a set of beans. + *

+ * This object is stateful and cannot be reused. After a terminal opration is performed, the underlying stream is considered + * consumed, and can no longer be used. + *

+ * This construct is not threadsafe. + */ +public final class BeanStream implements Iterable { + + private Stream stream; + + public BeanStream(Collection beans) { + this.stream = Objects.requireNonNull(beans, "Beans collection is null").stream(); + } + + public BeanStream withScope(Class scope) { + return withScope(DotName.createSimple(scope.getName())); + } + + public BeanStream withScope(DotName scopeName) { + stream = stream.filter(bean -> bean.getScope().getDotName().equals(scopeName)); + return this; + } + + public BeanStream withBeanType(Class beanType) { + return withBeanType(DotName.createSimple(beanType.getName())); + } + + public BeanStream withBeanType(DotName beanTypeName) { + stream = stream.filter(bean -> bean.getTypes().stream().anyMatch(t -> t.name().equals(beanTypeName))); + return this; + } + + public BeanStream withBeanType(Type beanType) { + stream = stream.filter(bean -> bean.getTypes().stream().anyMatch(t -> t.equals(beanType))); + return this; + } + + public BeanStream withBeanClass(Class beanClass) { + return withBeanClass(DotName.createSimple(beanClass.getName())); + } + + public BeanStream withBeanClass(DotName beanClass) { + stream = stream.filter(bean -> bean.getBeanClass().equals(beanClass)); + return this; + } + + public BeanStream withQualifier(Class qualifier) { + return withQualifier(DotName.createSimple(qualifier.getName())); + } + + public BeanStream withQualifier(DotName qualifierName) { + stream = stream.filter(bean -> bean.getQualifiers().stream().anyMatch(q -> q.name().equals(qualifierName))); + return this; + } + + public BeanStream withName(String name) { + stream = stream.filter(bean -> name.equals(bean.getName())); + return this; + } + + public BeanStream withIdentifier(String id) { + stream = stream.filter(bean -> id.equals(bean.getIdentifier())); + return this; + } + + public BeanStream producers() { + stream = stream.filter(bean -> bean.isProducerField() || bean.isProducerMethod()); + return this; + } + + public BeanStream producerMethods() { + stream = stream.filter(BeanInfo::isProducerMethod); + return this; + } + + public BeanStream producerFields() { + stream = stream.filter(BeanInfo::isProducerField); + return this; + } + + public BeanStream classBeans() { + stream = stream.filter(BeanInfo::isClassBean); + return this; + } + + public BeanStream syntheticBeans() { + stream = stream.filter(BeanInfo::isSynthetic); + return this; + } + + public BeanStream namedBeans() { + stream = stream.filter(bean -> bean.getName() != null); + return this; + } + + public BeanStream defaultBeans() { + stream = stream.filter(BeanInfo::isDefaultBean); + return this; + } + + public BeanStream alternativeBeans() { + stream = stream.filter(BeanInfo::isAlternative); + return this; + } + + /** + * Terminal operation. + * + * @return true if contains no elements + */ + public List collect() { + return stream.collect(Collectors.toList()); + } + + /** + * + * @return the underlying stream instance + */ + public Stream stream() { + return stream; + } + + /** + * Terminal operation. + * + * @return true if contains no elements + */ + public boolean isEmpty() { + return stream.count() == 0; + } + + /** + * Terminal operation. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return stream.iterator(); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java index 9eb2cf21a84f80..0c24459d0b379f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java @@ -1,6 +1,6 @@ package io.quarkus.arc.processor; -import java.util.List; +import java.util.Collection; import java.util.Map; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -51,11 +51,12 @@ interface Key { // Built-in keys static String BUILT_IN_PREFIX = BuildExtension.class.getPackage().getName() + "."; static Key INDEX = new SimpleKey<>(BUILT_IN_PREFIX + "index"); - static Key> INJECTION_POINTS = new SimpleKey<>(BUILT_IN_PREFIX + "injectionPoints"); - static Key> BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "beans"); - static Key> OBSERVERS = new SimpleKey<>(BUILT_IN_PREFIX + "observers"); + static Key> INJECTION_POINTS = new SimpleKey<>(BUILT_IN_PREFIX + "injectionPoints"); + static Key> BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "beans"); + static Key> REMOVED_BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "removedBeans"); + static Key> OBSERVERS = new SimpleKey<>(BUILT_IN_PREFIX + "observers"); static Key ANNOTATION_STORE = new SimpleKey<>(BUILT_IN_PREFIX + "annotationStore"); - static Key> SCOPES = new SimpleKey<>(BUILT_IN_PREFIX + "scopes"); + static Key> SCOPES = new SimpleKey<>(BUILT_IN_PREFIX + "scopes"); static Key> QUALIFIERS = new SimpleKey<>(BUILT_IN_PREFIX + "qualifiers"); static Key> INTERCEPTOR_BINDINGS = new SimpleKey<>(BUILT_IN_PREFIX + "interceptorBindings"); static Key> STEREOTYPES = new SimpleKey<>(BUILT_IN_PREFIX + "stereotypes"); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java index 7e1f4bd4486eb4..4e4f9e664afde0 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java @@ -1,6 +1,7 @@ package io.quarkus.arc.test.build.extension.beans; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import io.quarkus.arc.Arc; @@ -19,7 +20,7 @@ public class BeanRegistrarTest { @RegisterExtension - public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class) + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class).removeUnusedBeans(true) .beanRegistrars(new TestRegistrar()).build(); @Test @@ -39,18 +40,16 @@ public boolean initialize(BuildContext buildContext) { @Override public void register(RegistrationContext registrationContext) { - // Verify that the class bean was registered - assertTrue(registrationContext.get(Key.BEANS).stream() - .anyMatch(b -> b.isClassBean() && b.getBeanClass().toString().equals(UselessBean.class.getName()))); + assertFalse(registrationContext.beans().withBeanClass(UselessBean.class).isEmpty()); BeanConfigurator integerConfigurator = registrationContext.configure(Integer.class); - integerConfigurator.types(Integer.class).creator(mc -> { + integerConfigurator.unremovable().types(Integer.class).creator(mc -> { ResultHandle ret = mc.newInstance(MethodDescriptor.ofConstructor(Integer.class, int.class), mc.load(152)); mc.returnValue(ret); }); integerConfigurator.done(); - registrationContext.configure(String.class).types(String.class).param("name", "Frantisek") + registrationContext.configure(String.class).unremovable().types(String.class).param("name", "Frantisek") .creator(StringCreator.class).done(); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java index 3efddc52d03637..542fa4511af3d6 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java @@ -1,20 +1,27 @@ package io.quarkus.arc.test.build.extension.validator; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import io.quarkus.arc.Arc; import io.quarkus.arc.BeanCreator; import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BeanRegistrar; +import io.quarkus.arc.processor.ObserverInfo; import io.quarkus.arc.test.ArcTestContainer; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Initialized; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.inject.Inject; +import javax.inject.Named; import org.jboss.jandex.DotName; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; @@ -25,8 +32,9 @@ public class BeanDeploymentValidatorTest { @RegisterExtension - public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Alpha.class) + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Alpha.class, UselessBean.class) .beanRegistrars(new TestRegistrar()) + .removeUnusedBeans(true) .beanDeploymentValidators(new TestValidator()).build(); @Test @@ -48,16 +56,23 @@ static class TestValidator implements BeanDeploymentValidator { @Override public void validate(ValidationContext validationContext) { - assertTrue(validationContext.get(Key.BEANS) - .stream() - .anyMatch(b -> b.isClassBean() && b.getBeanClass() - .toString() - .equals(Alpha.class.getName()))); - assertTrue(validationContext.get(Key.BEANS) - .stream() - .anyMatch(b -> b.isSynthetic() && b.getTypes() - .contains(EmptyStringListCreator.listStringType()))); - assertTrue(validationContext.get(Key.OBSERVERS) + assertFalse(validationContext.removedBeans().withBeanClass(UselessBean.class).isEmpty()); + + assertFalse(validationContext.beans().classBeans().withBeanClass(Alpha.class).isEmpty()); + assertFalse(validationContext.beans().syntheticBeans().withBeanType(EmptyStringListCreator.listStringType()) + .isEmpty()); + List namedAlpha = validationContext.beans().withName("alpha").collect(); + assertEquals(1, namedAlpha.size()); + assertEquals(Alpha.class.getName(), namedAlpha.get(0).getBeanClass().toString()); + + Collection observers = validationContext.get(Key.OBSERVERS); + + List hasObservers = validationContext.beans().classBeans().namedBeans().stream() + .filter(b -> observers.stream().anyMatch(o -> o.getDeclaringBean().equals(b))).collect(Collectors.toList()); + assertEquals(1, hasObservers.size()); + assertEquals(Alpha.class.getName(), hasObservers.get(0).getBeanClass().toString()); + + assertTrue(observers .stream() .anyMatch(o -> o.getObservedType() .equals(Type.create(DotName.createSimple(Object.class.getName()), Kind.CLASS)))); @@ -66,11 +81,12 @@ public void validate(ValidationContext validationContext) { } + @Named @ApplicationScoped static class Alpha { @Inject - private List strings; + List strings; void observeAppContextInit(@Observes @Initialized(ApplicationScoped.class) Object event) { } @@ -96,4 +112,9 @@ public List create(CreationalContext> creationalContext, Ma } + @ApplicationScoped + static class UselessBean { + + } + }