Skip to content

Commit

Permalink
ArC - support CDI decorators
Browse files Browse the repository at this point in the history
- resolves #15490

Update independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java

Co-authored-by: Ladislav Thon <[email protected]>
  • Loading branch information
mkouba and Ladicek committed Mar 30, 2021
1 parent 08f87c9 commit b1dac80
Show file tree
Hide file tree
Showing 36 changed files with 2,335 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class BeanDeployment {
private final List<BeanInfo> beans;

private final List<InterceptorInfo> interceptors;
private final List<DecoratorInfo> decorators;

private final List<ObserverInfo> observers;

Expand Down Expand Up @@ -178,6 +179,7 @@ public class BeanDeployment {

this.injectionPoints = new CopyOnWriteArrayList<>();
this.interceptors = new CopyOnWriteArrayList<>();
this.decorators = new CopyOnWriteArrayList<>();
this.beans = new CopyOnWriteArrayList<>();
this.observers = new CopyOnWriteArrayList<>();

Expand Down Expand Up @@ -233,6 +235,7 @@ BeanRegistrar.RegistrationContext registerBeans(List<BeanRegistrar> beanRegistra
buildContextPut(Key.OBSERVERS.asString(), Collections.unmodifiableList(observers));

this.interceptors.addAll(findInterceptors(injectionPoints));
this.decorators.addAll(findDecorators(injectionPoints));
this.injectionPoints.addAll(injectionPoints);
buildContextPut(Key.INJECTION_POINTS.asString(), Collections.unmodifiableList(this.injectionPoints));

Expand All @@ -254,6 +257,10 @@ void init(Consumer<BytecodeTransformer> bytecodeTransformerConsumer,
for (InterceptorInfo interceptor : interceptors) {
interceptor.init(errors, bytecodeTransformerConsumer, transformUnproxyableClasses);
}
for (DecoratorInfo decorator : decorators) {
decorator.init(errors, bytecodeTransformerConsumer, transformUnproxyableClasses);
}

processErrors(errors);
List<Predicate<BeanInfo>> allUnusedExclusions = new ArrayList<>(additionalUnusedBeanExclusions);
if (unusedExclusions != null) {
Expand Down Expand Up @@ -391,6 +398,10 @@ public Collection<InterceptorInfo> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}

public Collection<DecoratorInfo> getDecorators() {
return Collections.unmodifiableList(decorators);
}

public Collection<StereotypeInfo> getStereotypes() {
return Collections.unmodifiableCollection(stereotypes.values());
}
Expand Down Expand Up @@ -1128,6 +1139,33 @@ private List<InterceptorInfo> findInterceptors(List<InjectionPointInfo> injectio
return interceptors;
}

private List<DecoratorInfo> findDecorators(List<InjectionPointInfo> injectionPoints) {
Map<DotName, ClassInfo> decoratorClasses = new HashMap<>();
for (AnnotationInstance annotation : beanArchiveIndex.getAnnotations(DotNames.DECORATOR)) {
if (Kind.CLASS.equals(annotation.target().kind())) {
decoratorClasses.put(annotation.target().asClass().name(), annotation.target().asClass());
}
}
List<DecoratorInfo> decorators = new ArrayList<>();
for (ClassInfo decoratorClass : decoratorClasses.values()) {
if (annotationStore.hasAnnotation(decoratorClass, DotNames.VETOED) || isExcluded(decoratorClass)) {
// Skip vetoed decorators
continue;
}
decorators
.add(Decorators.createDecorator(decoratorClass, this, injectionPointTransformer, annotationStore));
}
if (LOGGER.isTraceEnabled()) {
for (DecoratorInfo decorator : decorators) {
LOGGER.logf(Level.TRACE, "Created %s", decorator);
}
}
for (DecoratorInfo decorator : decorators) {
injectionPoints.addAll(decorator.getAllInjectionPoints());
}
return decorators;
}

private void validateBeans(List<Throwable> errors, List<BeanDeploymentValidator> validators,
Consumer<BytecodeTransformer> bytecodeTransformerConsumer) {

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName;

import io.quarkus.arc.processor.Methods.MethodKey;
import io.quarkus.arc.processor.Methods.SubclassSkipPredicate;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -18,6 +22,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.enterprise.inject.spi.DefinitionException;
import javax.enterprise.inject.spi.DeploymentException;
import javax.enterprise.inject.spi.InterceptionType;
import org.jboss.jandex.AnnotationInstance;
Expand All @@ -39,15 +44,15 @@ public class BeanInfo implements InjectionTargetInfo {

private final Type providerType;

private final Optional<AnnotationTarget> target;
protected final Optional<AnnotationTarget> target;

private final BeanDeployment beanDeployment;

private final ScopeInfo scope;
protected final ScopeInfo scope;

private final Set<Type> types;
protected final Set<Type> types;

private final Set<AnnotationInstance> qualifiers;
protected final Set<AnnotationInstance> qualifiers;

private final List<Injection> injections;

Expand All @@ -56,6 +61,7 @@ public class BeanInfo implements InjectionTargetInfo {
private final DisposerInfo disposer;

private final Map<MethodInfo, InterceptionInfo> interceptedMethods;
private final Map<MethodInfo, DecorationInfo> decoratedMethods;

private final Map<InterceptionType, InterceptionInfo> lifecycleInterceptors;

Expand Down Expand Up @@ -132,6 +138,7 @@ public class BeanInfo implements InjectionTargetInfo {
// Identifier must be unique for a specific deployment
this.identifier = Hashes.sha1(toString());
this.interceptedMethods = new ConcurrentHashMap<>();
this.decoratedMethods = new ConcurrentHashMap<>();
this.lifecycleInterceptors = new ConcurrentHashMap<>();
}

Expand Down Expand Up @@ -196,6 +203,10 @@ public boolean isInterceptor() {
return false;
}

public boolean isDecorator() {
return false;
}

public BeanInfo getDeclaringBean() {
return declaringBean;
}
Expand Down Expand Up @@ -257,6 +268,44 @@ Map<MethodInfo, InterceptionInfo> getInterceptedMethods() {
return interceptedMethods;
}

Map<MethodInfo, DecorationInfo> getDecoratedMethods() {
return decoratedMethods;
}

List<MethodInfo> getInterceptedOrDecoratedMethods() {
Set<MethodInfo> methods = new HashSet<>(interceptedMethods.keySet());
methods.addAll(decoratedMethods.keySet());
List<MethodInfo> sorted = new ArrayList<>(methods);
Collections.sort(sorted, Comparator.comparing(MethodInfo::toString));
return sorted;
}

Set<MethodInfo> getDecoratedMethods(DecoratorInfo decorator) {
Set<MethodInfo> decorated = new HashSet<>();
for (Entry<MethodInfo, DecorationInfo> entry : decoratedMethods.entrySet()) {
if (entry.getValue().decorators.contains(decorator)) {
decorated.add(entry.getKey());
}
}
return decorated;
}

// Returns a map of method descriptor -> next decorator in the chain
// e.g. foo() -> BravoDecorator
Map<MethodDescriptor, DecoratorInfo> getNextDecorators(DecoratorInfo decorator) {
Map<MethodDescriptor, DecoratorInfo> next = new HashMap<>();
for (Entry<MethodInfo, DecorationInfo> entry : decoratedMethods.entrySet()) {
List<DecoratorInfo> decorators = entry.getValue().decorators;
int index = decorators.indexOf(decorator);
if (index != -1) {
if (index != (decorators.size() - 1)) {
next.put(MethodDescriptor.of(entry.getKey()), decorators.get(index + 1));
}
}
}
return next;
}

InterceptionInfo getLifecycleInterceptors(InterceptionType interceptionType) {
return lifecycleInterceptors.containsKey(interceptionType) ? lifecycleInterceptors.get(interceptionType)
: InterceptionInfo.EMPTY;
Expand All @@ -271,7 +320,8 @@ public boolean hasAroundInvokeInterceptors() {
}

boolean isSubclassRequired() {
return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY);
return !interceptedMethods.isEmpty() || !decoratedMethods.isEmpty()
|| lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY);
}

boolean hasDefaultDestroy() {
Expand Down Expand Up @@ -314,6 +364,27 @@ List<InterceptorInfo> getBoundInterceptors() {
return bound;
}

List<DecoratorInfo> getBoundDecorators() {
if (decoratedMethods.isEmpty()) {
return Collections.emptyList();
}
List<DecoratorInfo> bound = new ArrayList<>();
for (DecorationInfo decoration : decoratedMethods.values()) {
for (DecoratorInfo decorator : decoration.decorators) {
if (!bound.contains(decorator)) {
bound.add(decorator);
}
}
}
// Sort by priority (highest goes first) and by bean class
// Highest priority first because the decorators are instantiated in the reverse order,
// i.e. when the subclass constructor is generated the delegate subclass of the first decorator
// (lower priority) needs a reference to the next decorator in the chain (higher priority)
Collections.sort(bound,
Comparator.comparing(DecoratorInfo::getPriority).reversed().thenComparing(DecoratorInfo::getBeanClass));
return bound;
}

public DisposerInfo getDisposer() {
return disposer;
}
Expand Down Expand Up @@ -375,13 +446,18 @@ void init(List<Throwable> errors, Consumer<BytecodeTransformer> bytecodeTransfor
boolean transformUnproxyableClasses) {
for (Injection injection : injections) {
for (InjectionPointInfo injectionPoint : injection.injectionPoints) {
if (injectionPoint.isDelegate() && !isDecorator()) {
errors.add(new DeploymentException(String.format(
"Only decorators can declare a delegate injection point: %s", this)));
}
Beans.resolveInjectionPoint(beanDeployment, this, injectionPoint, errors);
}
}
if (disposer != null) {
disposer.init(errors);
}
interceptedMethods.putAll(initInterceptedMethods(errors, bytecodeTransformerConsumer, transformUnproxyableClasses));
decoratedMethods.putAll(initDecoratedMethods());
if (errors.isEmpty()) {
lifecycleInterceptors.putAll(initLifecycleInterceptors());
}
Expand All @@ -401,7 +477,7 @@ protected String getType() {

private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable> errors,
Consumer<BytecodeTransformer> bytecodeTransformerConsumer, boolean transformUnproxyableClasses) {
if (!isInterceptor() && isClassBean()) {
if (!isInterceptor() && !isDecorator() && isClassBean()) {
Map<MethodInfo, InterceptionInfo> interceptedMethods = new HashMap<>();
Map<MethodKey, Set<AnnotationInstance>> candidates = new HashMap<>();

Expand Down Expand Up @@ -435,6 +511,91 @@ private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable>
}
}

private Map<MethodInfo, DecorationInfo> initDecoratedMethods() {
Collection<DecoratorInfo> decorators = beanDeployment.getDecorators();
if (decorators.isEmpty() || isInterceptor() || isDecorator() || !isClassBean()) {
return Collections.emptyMap();
}
// A decorator is bound to a bean if the bean is assignable to the delegate injection point
List<DecoratorInfo> bound = new LinkedList<>();
for (DecoratorInfo decorator : decorators) {
if (Beans.matches(this, decorator.getDelegateInjectionPoint().getTypeAndQualifiers())) {
bound.add(decorator);
}
}
// Decorators with the smaller priority values are called first
Collections.sort(bound, Comparator.comparingInt(DecoratorInfo::getPriority).thenComparing(DecoratorInfo::getBeanClass));

Map<MethodKey, DecorationInfo> candidates = new HashMap<>();
addDecoratedMethods(candidates, target.get().asClass(), bound,
new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom));

Map<MethodInfo, DecorationInfo> decoratedMethods = new HashMap<>(candidates.size());
for (Entry<MethodKey, DecorationInfo> entry : candidates.entrySet()) {
decoratedMethods.put(entry.getKey().method, entry.getValue());
}
return decoratedMethods;
}

private void addDecoratedMethods(Map<MethodKey, DecorationInfo> decoratedMethods, ClassInfo classInfo,
List<DecoratorInfo> boundDecorators, SubclassSkipPredicate skipPredicate) {
skipPredicate.startProcessing(classInfo);
for (MethodInfo method : classInfo.methods()) {
if (skipPredicate.test(method)) {
continue;
}
List<DecoratorInfo> matching = findMatchingDecorators(method, boundDecorators);
if (!matching.isEmpty()) {
decoratedMethods.computeIfAbsent(new MethodKey(method), key -> new DecorationInfo(matching));
}
}
skipPredicate.methodsProcessed();
if (!classInfo.superName().equals(DotNames.OBJECT)) {
ClassInfo superClassInfo = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName());
if (superClassInfo != null) {
addDecoratedMethods(decoratedMethods, superClassInfo, boundDecorators, skipPredicate);
}
}
}

List<DecoratorInfo> findMatchingDecorators(MethodInfo method, List<DecoratorInfo> decorators) {
List<Type> methodParams = method.parameters();
List<DecoratorInfo> matching = new ArrayList<>(decorators.size());
for (DecoratorInfo decorator : decorators) {
for (Type decoratedType : decorator.getDecoratedTypes()) {
// Converter<String>
ClassInfo decoratedTypeClass = decorator.getDeployment().getBeanArchiveIndex()
.getClassByName(decoratedType.name());
if (decoratedTypeClass == null) {
throw new DefinitionException(
"The class of the decorated type " + decoratedType + " was not found in the index");
}
for (MethodInfo decoratedMethod : decoratedTypeClass.methods()) {
if (!method.name().equals(decoratedMethod.name())) {
continue;
}
List<Type> decoratedMethodParams = decoratedMethod.parameters();
if (methodParams.size() != decoratedMethodParams.size()) {
continue;
}
// Match the resolved parameter types
boolean matches = true;
decoratedMethodParams = Types.getResolvedParameters(decoratedTypeClass, method,
beanDeployment.getBeanArchiveIndex());
for (int i = 0; i < methodParams.size(); i++) {
if (!methodParams.get(i).equals(decoratedMethodParams.get(i))) {
matches = false;
}
}
if (matches) {
matching.add(decorator);
}
}
}
}
return matching;
}

private Map<InterceptionType, InterceptionInfo> initLifecycleInterceptors() {
if (!isInterceptor() && isClassBean()) {
Map<InterceptionType, InterceptionInfo> lifecycleInterceptors = new HashMap<>();
Expand Down Expand Up @@ -579,6 +740,20 @@ boolean isEmpty() {

}

static class DecorationInfo {

final List<DecoratorInfo> decorators;

public DecorationInfo(List<DecoratorInfo> decorators) {
this.decorators = decorators;
}

boolean isEmpty() {
return decorators.isEmpty();
}

}

static class Builder {

private ClassInfo implClazz;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ public List<Resource> generateResources(ReflectionRegistration reflectionRegistr
InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate,
privateMembers, generateSources, reflectionRegistration, existingClasses, beanToGeneratedName,
injectionPointAnnotationsPredicate);
DecoratorGenerator decoratorGenerator = new DecoratorGenerator(annotationLiterals, applicationClassPredicate,
privateMembers, generateSources, reflectionRegistration, existingClasses, beanToGeneratedName,
injectionPointAnnotationsPredicate);
SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate,
generateSources, reflectionRegistration, existingClasses);
ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate,
Expand All @@ -175,7 +178,12 @@ public List<Resource> generateResources(ReflectionRegistration reflectionRegistr
resources.add(resource);
}
}

// Generate decorators
for (DecoratorInfo decorator : beanDeployment.getDecorators()) {
for (Resource resource : decoratorGenerator.generate(decorator)) {
resources.add(resource);
}
}
// Generate beans
for (BeanInfo bean : beanDeployment.getBeans()) {
for (Resource resource : beanGenerator.generate(bean)) {
Expand Down
Loading

0 comments on commit b1dac80

Please sign in to comment.