Skip to content

Commit

Permalink
Merge pull request #5830 from mkouba/issue-5653
Browse files Browse the repository at this point in the history
ArC - introduce BeanStream
  • Loading branch information
mkouba authored Nov 29, 2019
2 parents 688134f + bb448c0 commit 204284d
Show file tree
Hide file tree
Showing 16 changed files with 518 additions and 131 deletions.
20 changes: 12 additions & 8 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
----
Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<InjectionPointInfo>` containing all injection points
** `Collection<InjectionPointInfo>` containing all injection points
* `BEANS`
** `List<BeanInfo>` containing all beans
** `Collection<BeanInfo>` containing all beans
* `REMOVED_BEANS`
** `Collection<BeanInfo>` containing all the removed beans; see <<remove_unused_beans>> for more information
* `OBSERVERS`
** `List<ObserverInfo>` containing all observers
** `Collection<ObserverInfo>` containing all observers
* `SCOPES`
** `List<ScopeInfo>` containing all scopes, including custom ones
** `Collection<ScopeInfo>` containing all scopes, including custom ones
* `QUALIFIERS`
** `Map<DotName, ClassInfo>` containing all qualifiers
* `INTERCEPTOR_BINDINGS`
Expand All @@ -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

Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -192,10 +191,8 @@ public void transform(TransformationContext ctx) {
void validateFaultToleranceAnnotations(
ValidationPhaseBuildItem validationPhase, SmallryeFaultToleranceRecorder recorder) {
List<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type> 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<Type> 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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ public final class BeanConfigurator<T> {

private final Map<String, Object> params;

private boolean isDefaultBean;
private boolean defaultBean;

private boolean removable;

/**
*
Expand All @@ -77,6 +79,7 @@ public final class BeanConfigurator<T> {
this.scope = BuiltinScope.DEPENDENT.getInfo();
this.params = new HashMap<>();
this.name = null;
this.removable = true;
}

public BeanConfigurator<T> param(String name, Class<?> value) {
Expand Down Expand Up @@ -152,7 +155,12 @@ public BeanConfigurator<T> name(String name) {
}

public BeanConfigurator<T> defaultBean() {
this.isDefaultBean = true;
this.defaultBean = true;
return this;
}

public BeanConfigurator<T> unremovable() {
this.removable = false;
return this;
}

Expand Down Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ void init(Consumer<BytecodeTransformer> bytecodeTransformerConsumer) {
if (bean.getName() != null) {
continue test;
}
// Unremovable synthetic beans
if (!bean.isRemovable()) {
continue test;
}
// Custom exclusions
for (Predicate<BeanInfo> exclusion : unusedExclusions) {
if (exclusion.test(bean)) {
Expand Down Expand Up @@ -294,6 +298,9 @@ void init(Consumer<BytecodeTransformer> 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);
}

Expand Down Expand Up @@ -836,6 +843,11 @@ public <V> V put(Key<V> 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);
Expand Down Expand Up @@ -956,6 +968,16 @@ public List<Throwable> 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));
}

}

}
Loading

0 comments on commit 204284d

Please sign in to comment.