Skip to content

Commit

Permalink
ArC - introduce AutoAddScopeBuildItem#priority
Browse files Browse the repository at this point in the history
- a build item with higher priority takes precedence
- also do not add scope if already added by another build item
  • Loading branch information
mkouba committed Dec 16, 2021
1 parent 39e2e8b commit 64a8c0b
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.arc.deployment;

import java.util.Collection;
import java.util.function.BiConsumer;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
Expand All @@ -14,7 +15,7 @@

/**
* This build item can be used to turn a class that is not annotated with a CDI scope annotation into a bean, i.e. the default
* scope annotation is added automatically if conditions are met.
* scope annotation is added automatically if all conditions are met.
*/
public final class AutoAddScopeBuildItem extends MultiBuildItem {

Expand All @@ -27,14 +28,19 @@ public static Builder builder() {
private final DotName defaultScope;
private final boolean unremovable;
private final String reason;
private final int priority;
private final BiConsumer<DotName, String> scopeAlreadyAdded;

private AutoAddScopeBuildItem(MatchPredicate matchPredicate, boolean containerServicesRequired,
DotName defaultScope, boolean unremovable, String reason) {
DotName defaultScope, boolean unremovable, String reason, int priority,
BiConsumer<DotName, String> scopeAlreadyAdded) {
this.matchPredicate = matchPredicate;
this.containerServicesRequired = containerServicesRequired;
this.defaultScope = defaultScope;
this.unremovable = unremovable;
this.reason = reason;
this.priority = priority;
this.scopeAlreadyAdded = scopeAlreadyAdded;
}

public boolean isContainerServicesRequired() {
Expand All @@ -50,7 +56,15 @@ public boolean isUnremovable() {
}

public String getReason() {
return reason != null ? ": " + reason : "";
return reason != null ? reason : "unknown";
}

public int getPriority() {
return priority;
}

public BiConsumer<DotName, String> getScopeAlreadyAdded() {
return scopeAlreadyAdded;
}

public boolean test(ClassInfo clazz, Collection<AnnotationInstance> annotations, IndexView index) {
Expand Down Expand Up @@ -86,11 +100,14 @@ public static class Builder {
private DotName defaultScope;
private boolean unremovable;
private String reason;
private int priority;
private BiConsumer<DotName, String> scopeAlreadyAdded;

private Builder() {
this.defaultScope = BuiltinScope.DEPENDENT.getName();
this.unremovable = false;
this.requiresContainerServices = false;
this.priority = 0;
}

/**
Expand Down Expand Up @@ -222,6 +239,32 @@ public Builder reason(String reason) {
return this;
}

/**
* Set the priority. The default priority is {@code 0}. An {@link AutoAddScopeBuildItem} with higher priority takes
* precedence.
*
* @param priority
* @return self
*/
public Builder priority(int priority) {
this.priority = priority;
return this;
}

/**
* If a scope was already added by another {@link AutoAddScopeBuildItem} then this consumer is used to handle this
* situation, i.e. log a warning or throw an exception. The first argument is the
* {@link AutoAddScopeBuildItem#getDefaultScope()} and the second argument is the
* {@link AutoAddScopeBuildItem#getReason()}.
*
* @param consumer
* @return self
*/
public Builder scopeAlreadyAdded(BiConsumer<DotName, String> consumer) {
this.scopeAlreadyAdded = consumer;
return this;
}

private Builder and(MatchPredicate other) {
if (matchPredicate == null) {
matchPredicate = other;
Expand All @@ -235,7 +278,8 @@ public AutoAddScopeBuildItem build() {
if (matchPredicate == null) {
throw new IllegalStateException("A matching predicate must be set!");
}
return new AutoAddScopeBuildItem(matchPredicate, requiresContainerServices, defaultScope, unremovable, reason);
return new AutoAddScopeBuildItem(matchPredicate, requiresContainerServices, defaultScope, unremovable, reason,
priority, scopeAlreadyAdded);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.quarkus.arc.deployment;

import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -31,6 +33,10 @@ void annotationTransformer(List<AutoAddScopeBuildItem> autoScopes, CustomScopeAn
if (autoScopes.isEmpty()) {
return;
}

List<AutoAddScopeBuildItem> sortedAutoScopes = autoScopes.stream()
.sorted(Comparator.comparingInt(AutoAddScopeBuildItem::getPriority).reversed()).collect(Collectors.toList());

Set<DotName> containerAnnotationNames = autoInjectAnnotations.stream().flatMap(a -> a.getAnnotationNames().stream())
.collect(Collectors.toSet());
containerAnnotationNames.add(DotNames.POST_CONSTRUCT);
Expand All @@ -46,18 +52,27 @@ public boolean appliesTo(Kind kind) {
return kind == Kind.CLASS;
}

@Override
public int getPriority() {
// Make sure this annotation transformer runs before all transformers with the default priority
return DEFAULT_PRIORITY + 1000;
}

@Override
public void transform(TransformationContext context) {
if (scopes.isScopeIn(context.getAnnotations())) {
// Skip classes annotated with a scope
return;
}
ClassInfo clazz = context.getTarget().asClass();
DotName scope = null;
Boolean requiresContainerServices = null;
String reason = null;

for (AutoAddScopeBuildItem autoScope : autoScopes) {
for (AutoAddScopeBuildItem autoScope : sortedAutoScopes) {
if (autoScope.isContainerServicesRequired()) {
if (requiresContainerServices == null) {
// Analyze the class hierarchy lazily
requiresContainerServices = requiresContainerServices(clazz, containerAnnotationNames,
beanArchiveIndex.getIndex());
}
Expand All @@ -67,13 +82,22 @@ public void transform(TransformationContext context) {
}
}
if (autoScope.test(clazz, context.getAnnotations(), beanArchiveIndex.getIndex())) {
context.transform().add(autoScope.getDefaultScope()).done();
if (scope != null) {
BiConsumer<DotName, String> consumer = autoScope.getScopeAlreadyAdded();
if (consumer != null) {
consumer.accept(scope, reason);
} else {
LOGGER.debugf("Scope %s was already added for reason: %s", scope, reason);
}
continue;
}
scope = autoScope.getDefaultScope();
reason = autoScope.getReason();
context.transform().add(scope).done();
if (autoScope.isUnremovable()) {
unremovables.add(clazz.name());
}
LOGGER.debugf("Automatically added scope %s to class %s" + autoScope.getReason(),
autoScope.getDefaultScope(), clazz, autoScope.getReason());
break;
LOGGER.debugf("Automatically added scope %s to class %s: %s", scope, clazz, autoScope.getReason());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.arc.deployment;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;

import org.jboss.jandex.AnnotationInstance;
Expand Down Expand Up @@ -84,4 +85,21 @@ public boolean isScopeDeclaredOn(ClassInfo clazz) {
public boolean isScopeIn(Collection<AnnotationInstance> annotations) {
return !annotations.isEmpty() && (BuiltinScope.isIn(annotations) || isCustomScopeIn(annotations));
}

/**
*
* @param annotations
* @return the scope or empty optional
*/
public Optional<AnnotationInstance> getScope(Collection<AnnotationInstance> annotations) {
if (annotations.isEmpty()) {
return Optional.empty();
}
for (AnnotationInstance annotationInstance : annotations) {
if (BuiltinScope.from(annotationInstance.name()) != null || customScopeNames.contains(annotationInstance.name())) {
return Optional.of(annotationInstance);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.quarkus.arc.test.autoscope;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.UUID;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.deployment.AutoAddScopeBuildItem;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.test.QuarkusUnitTest;

public class AutoScopeBuildItemTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addClasses(SimpleBean.class))
.addBuildChainCustomizer(b -> {
b.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(AutoAddScopeBuildItem.builder().match((clazz, annotations, index) -> {
return clazz.name().toString().equals(SimpleBean.class.getName());
}).defaultScope(BuiltinScope.DEPENDENT)
.scopeAlreadyAdded((scope, reason) -> {
// We cant's pass the state directly to AutoScopeBuildItemTest because it's loaded by a different classloader
Logger.getLogger("AutoScopeBuildItemTest").info(scope + ":" + reason);
}).build());
}
}).produces(AutoAddScopeBuildItem.class).build();
b.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(AutoAddScopeBuildItem.builder().match((clazz, annotations, index) -> {
return clazz.name().toString().equals(SimpleBean.class.getName());
}).defaultScope(BuiltinScope.SINGLETON).priority(10).reason("Foo!").build());
}
}).produces(AutoAddScopeBuildItem.class).build();
}).setLogRecordPredicate(log -> "AutoScopeBuildItemTest".equals(log.getLoggerName()))
.assertLogRecords(records -> {
assertEquals(1, records.size());
assertEquals("javax.inject.Singleton:Foo!", records.get(0).getMessage());
});

@Inject
Instance<SimpleBean> instance;

@Test
public void testBean() {
assertTrue(instance.isResolvable());
// The scope should be @Singleton
assertEquals(instance.get().ping(), instance.get().ping());
}

static class SimpleBean {

private String id;

public String ping() {
return id;
}

@PostConstruct
void init() {
id = UUID.randomUUID().toString();
}

}

}

0 comments on commit 64a8c0b

Please sign in to comment.