Skip to content

Commit

Permalink
Merge pull request #26153 from mkouba/issue-26065
Browse files Browse the repository at this point in the history
ArC Dev UI - add bean dependency graph
  • Loading branch information
gsmet committed Jun 16, 2022
2 parents 0795d3a + e43a6eb commit ec1d5b7
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 7 deletions.
6 changes: 6 additions & 0 deletions build-parent/pom.xml
Expand Up @@ -137,6 +137,7 @@
<webjar.codemirror.version>5.62.2</webjar.codemirror.version>
<!-- we don't add mermaid as a dependency as it brings a ton of things we don't use -->
<webjar.mermaid.version>8.9.1</webjar.mermaid.version>
<webjar.d3js.version>6.6.0</webjar.d3js.version>

<!-- revapi API check -->
<revapi-maven-plugin.version>0.14.6</revapi-maven-plugin.version>
Expand Down Expand Up @@ -315,6 +316,11 @@
<artifactId>codemirror</artifactId>
<version>${webjar.codemirror.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>d3js</artifactId>
<version>${webjar.d3js.version}</version>
</dependency>

<dependency>
<groupId>com.github.davidmoten</groupId>
Expand Down
Expand Up @@ -172,6 +172,11 @@ public boolean test(BeanInfo bean) {
return bean.getBeanClass().toString().equals(className);
}

@Override
public String toString() {
return "BeanClassNameExclusion [className=" + className + "]";
}

}

public static class BeanClassNamesExclusion implements Predicate<BeanInfo> {
Expand All @@ -187,6 +192,11 @@ public boolean test(BeanInfo bean) {
return classNames.contains(bean.getBeanClass().toString());
}

@Override
public String toString() {
return "BeanClassNamesExclusion [classNames=" + classNames + "]";
}

}

public static class BeanTypeExclusion implements Predicate<BeanInfo> {
Expand All @@ -202,6 +212,11 @@ public boolean test(BeanInfo bean) {
return bean.getTypes().stream().anyMatch(t -> dotName.equals(t.name()));
}

@Override
public String toString() {
return "BeanTypeExclusion [dotName=" + dotName + "]";
}

}

public static class BeanTypesExclusion implements Predicate<BeanInfo> {
Expand All @@ -217,6 +232,11 @@ public boolean test(BeanInfo bean) {
return bean.getTypes().stream().anyMatch(t -> dotNames.contains(t.name()));
}

@Override
public String toString() {
return "BeanTypesExclusion [dotNames=" + dotNames + "]";
}

}

public static class BeanClassAnnotationExclusion implements Predicate<BeanInfo> {
Expand Down Expand Up @@ -248,6 +268,11 @@ public boolean test(BeanInfo bean) {
return false;
}

@Override
public String toString() {
return "BeanClassAnnotationExclusion [nameStartsWith=" + nameStartsWith + ", name=" + name + "]";
}

}

}
Expand Up @@ -5,6 +5,7 @@

import io.quarkus.arc.processor.BeanDeploymentValidator;
import io.quarkus.arc.processor.BeanProcessor;
import io.quarkus.arc.processor.BeanResolver;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -33,6 +34,15 @@ public BeanDeploymentValidator.ValidationContext getContext() {
return context;
}

/**
* The bean resolver can be used to apply the type-safe resolution rules.
*
* @return the bean resolver
*/
public BeanResolver getBeanResolver() {
return beanProcessor.getBeanDeployment().getBeanResolver();
}

BeanProcessor getBeanProcessor() {
return beanProcessor;
}
Expand Down
@@ -1,9 +1,12 @@
package io.quarkus.arc.deployment.devconsole;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
Expand All @@ -15,11 +18,15 @@
import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem;
import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.devconsole.DependecyGraph.Link;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BeanDeploymentValidator;
import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BeanResolver;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.DecoratorInfo;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.InterceptorInfo;
import io.quarkus.arc.processor.ObserverInfo;
import io.quarkus.arc.runtime.ArcContainerSupplier;
Expand Down Expand Up @@ -124,6 +131,14 @@ public DevConsoleTemplateInfoBuildItem collectBeanInfo(ValidationPhaseBuildItem
beanInfos.addRemovedDecorator(DevDecoratorInfo.from(decorator, predicate));
}
}

// Build dependency graphs
BeanResolver resolver = validationPhaseBuildItem.getBeanResolver();
for (BeanInfo bean : validationContext.beans()) {
beanInfos.addDependencyGraph(bean.getIdentifier(),
buildDependencyGraph(bean, validationContext, resolver, beanInfos));
}

beanInfos.sort();
return new DevConsoleTemplateInfoBuildItem("devBeanInfos", beanInfos);
}
Expand All @@ -138,4 +153,70 @@ private boolean isAdditionalBeanDefiningAnnotationOn(ClassInfo beanClass,
return false;
}

DependecyGraph buildDependencyGraph(BeanInfo bean, ValidationContext validationContext, BeanResolver resolver,
DevBeanInfos devBeanInfos) {
Set<DevBeanInfo> nodes = new HashSet<>();
Collection<BeanInfo> beans = validationContext.get(BuildExtension.Key.BEANS);
Set<Link> links = new HashSet<>();
Map<BeanInfo, List<BeanInfo>> declaringToProducers = validationContext.beans().producers()
.collect(Collectors.groupingBy(BeanInfo::getDeclaringBean));
addNodesDependencies(bean, nodes, links, bean, devBeanInfos);
addNodesDependents(bean, nodes, links, bean, beans, declaringToProducers, resolver, devBeanInfos);
return new DependecyGraph(nodes, links);
}

void addNodesDependencies(BeanInfo root, Set<DevBeanInfo> nodes, Set<Link> links, BeanInfo bean,
DevBeanInfos devBeanInfos) {
if (nodes.add(devBeanInfos.getBean(bean.getIdentifier()))) {
if (bean.isProducerField() || bean.isProducerMethod()) {
links.add(Link.producer(bean.getIdentifier(), bean.getDeclaringBean().getIdentifier()));
addNodesDependencies(root, nodes, links, bean.getDeclaringBean(), devBeanInfos);
}
for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) {
BeanInfo resolved = injectionPoint.getResolvedBean();
if (resolved != null && !resolved.equals(bean)) {
links.add(Link.dependency(root.equals(bean), bean.getIdentifier(), resolved.getIdentifier()));
// add transient dependencies
addNodesDependencies(root, nodes, links, injectionPoint.getResolvedBean(), devBeanInfos);
}
}
}
}

void addNodesDependents(BeanInfo root, Set<DevBeanInfo> nodes, Set<Link> links, BeanInfo bean, Collection<BeanInfo> beans,
Map<BeanInfo, List<BeanInfo>> declaringToProducers, BeanResolver resolver, DevBeanInfos devBeanInfos) {
for (BeanInfo dependent : beans) {
if (!bean.equals(dependent)) {
for (InjectionPointInfo injectionPoint : dependent.getAllInjectionPoints()) {
Link link = null;
if (injectionPoint.isProgrammaticLookup()) {
if (resolver.matches(bean,
injectionPoint.getType().asParameterizedType().arguments().get(0),
injectionPoint.getRequiredQualifiers())) {
link = Link.lookup(dependent.getIdentifier(), bean.getIdentifier());
}
} else if (bean.equals(injectionPoint.getResolvedBean())) {
link = Link.dependent(root.equals(bean), dependent.getIdentifier(), bean.getIdentifier());
}
if (link != null) {
links.add(link);
if (nodes.add(devBeanInfos.getBean(dependent.getIdentifier()))) {
// add transient dependents
addNodesDependents(root, nodes, links, dependent, beans, declaringToProducers, resolver,
devBeanInfos);
}
}
}
}
}
for (BeanInfo producer : declaringToProducers.getOrDefault(bean, Collections.emptyList())) {
links.add(Link.producer(producer.getIdentifier(), bean.getIdentifier()));
if (nodes.add(devBeanInfos.getBean(producer.getIdentifier()))) {
// add transient dependents
addNodesDependents(root, nodes, links, producer, beans, declaringToProducers, resolver,
devBeanInfos);
}
}
}

}
@@ -0,0 +1,66 @@
package io.quarkus.arc.deployment.devconsole;

import java.util.Objects;
import java.util.Set;

public class DependecyGraph {

public final Set<DevBeanInfo> nodes;
public final Set<Link> links;

public DependecyGraph(Set<DevBeanInfo> nodes, Set<Link> links) {
this.nodes = nodes;
this.links = links;
}

public static class Link {

static Link dependent(boolean direct, String source, String target) {
return new Link(source, target, direct ? "directDependent" : "dependency");
}

static Link dependency(boolean direct, String source, String target) {
return new Link(source, target, direct ? "directDependency" : "dependency");
}

static Link lookup(String source, String target) {
return new Link(source, target, "lookup");
}

static Link producer(String source, String target) {
return new Link(source, target, "producer");
}

public final String source;
public final String target;
public final String type;

public Link(String source, String target, String type) {
this.source = source;
this.target = target;
this.type = type;
}

@Override
public int hashCode() {
return Objects.hash(source, target);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Link other = (Link) obj;
return Objects.equals(source, other.source) && Objects.equals(target, other.target);
}

}

}
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.jboss.jandex.AnnotationInstance;
Expand Down Expand Up @@ -70,17 +71,21 @@ public static DevBeanInfo from(BeanInfo bean, CompletedApplicationClassPredicate
} else {
throw new IllegalArgumentException("Invalid annotation target: " + target);
}
return new DevBeanInfo(kind, isApplicationBean, providerType, memberName, types, qualifiers, scope, declaringClass,
return new DevBeanInfo(bean.getIdentifier(), kind, isApplicationBean, providerType, memberName, types, qualifiers,
scope, declaringClass,
interceptors);
} else {
// Synthetic bean
return new DevBeanInfo(DevBeanKind.SYNTHETIC, false, providerType, null, types, qualifiers, scope, null,
return new DevBeanInfo(bean.getIdentifier(), DevBeanKind.SYNTHETIC, false, providerType, null, types, qualifiers,
scope, null,
interceptors);
}
}

public DevBeanInfo(DevBeanKind kind, boolean isApplicationBean, Name providerType, String memberName, Set<Name> types,
public DevBeanInfo(String id, DevBeanKind kind, boolean isApplicationBean, Name providerType, String memberName,
Set<Name> types,
Set<Name> qualifiers, Name scope, Name declaringClass, List<String> boundInterceptors) {
this.id = id;
this.kind = kind;
this.isApplicationBean = isApplicationBean;
this.providerType = providerType;
Expand All @@ -92,6 +97,7 @@ public DevBeanInfo(DevBeanKind kind, boolean isApplicationBean, Name providerTyp
this.interceptors = boundInterceptors;
}

private final String id;
private final DevBeanKind kind;
private final boolean isApplicationBean;
private final Name providerType;
Expand All @@ -102,6 +108,10 @@ public DevBeanInfo(DevBeanKind kind, boolean isApplicationBean, Name providerTyp
private final Name declaringClass;
private final List<String> interceptors;

public String getId() {
return id;
}

public DevBeanKind getKind() {
return kind;
}
Expand Down Expand Up @@ -151,6 +161,21 @@ public List<String> getInterceptors() {
return interceptors;
}

public String getDescription() {
switch (kind) {
case CLASS:
return providerType.toString();
case FIELD:
return declaringClass.toString() + "#" + memberName;
case METHOD:
return declaringClass.toString() + "#" + memberName + "()";
case SYNTHETIC:
return "Synthetic: " + providerType.toString();
default:
return providerType.toString();
}
}

@Override
public int compareTo(DevBeanInfo o) {
// Application beans should go first
Expand All @@ -159,4 +184,25 @@ public int compareTo(DevBeanInfo o) {
}
return isApplicationBean ? -1 : 1;
}

@Override
public int hashCode() {
return Objects.hash(id);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DevBeanInfo other = (DevBeanInfo) obj;
return Objects.equals(id, other.id);
}

}

0 comments on commit ec1d5b7

Please sign in to comment.