Skip to content

Commit

Permalink
ArC - register /arc/beans and /arc/observers routes in the dev mode
Browse files Browse the repository at this point in the history
- these routes produce basic debug info in the JSON format
  • Loading branch information
mkouba committed May 18, 2020
1 parent 328673e commit 9cac700
Show file tree
Hide file tree
Showing 12 changed files with 687 additions and 1 deletion.
15 changes: 15 additions & 0 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ Here is a summary of which extensions can access which metadata:
* `BeanDeploymentValidator`
** Has access to all build metadata

[[dev-mode]]
== Development Mode

In the development mode, two special endpoints are registered automatically to provide some basic debug info in the JSON format:

* HTTP GET `/arc/beans` - returns the list of all beans
** You can use query params to filter the output:
*** `scope` - include beans with scope that ends with the given value, i.e. `http://localhost:8080/arc/beans?scope=ApplicationScoped`
*** `beanClass` - include beans with bean class that starts with the given value, i.e. `http://localhost:8080/arc/beans?beanClass=org.acme.Foo`
*** `kind` - include beans of the specified kind (`CLASS`, `PRODUCER_FIELD`, `PRODUCER_METHOD`, `INTERCEPTOR` or `SYNTHETIC`), i.e. `http://localhost:8080/arc/beans?kind=PRODUCER_METHOD`
* HTTP GET `/arc/observers` - returns the list of all observer methods

NOTE: These endpoints are only available in the development mode, i.e. when you run your application via `mvn quarkus:dev` (or `./gradlew quarkusDev`).


[[arc-configuration-reference]]
== ArC Configuration Reference

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.vertx.http.deployment.devmode;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.runtime.devmode.ArcEndpointRecorder;

public class ArcEndpointProcessor {

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep(onlyIf = IsDevelopment.class)
void registerRoutes(ArcEndpointRecorder recorder, BuildProducer<RouteBuildItem> routes) {
routes.produce(new RouteBuildItem("/arc/beans", recorder.createBeansHandler()));
routes.produce(new RouteBuildItem("/arc/observers", recorder.createObserversHandler()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.vertx.http.devmode;

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

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Named;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.runtime.StartupEvent;
import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

public class ArcEndpointTest {

@RegisterExtension
static final QuarkusDevModeTest test = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Foo.class));

@Test
public void testBeans() {
JsonArray beans = new JsonArray(RestAssured.get("/arc/beans").asString());
JsonArray observers = new JsonArray(RestAssured.get("/arc/observers").asString());
JsonObject fooBean = null;
JsonObject fooObserver = null;
for (int i = 0; i < beans.size(); i++) {
JsonObject bean = beans.getJsonObject(i);
if (bean.getString("beanClass").equals(Foo.class.getName())) {
fooBean = bean;
}
}
assertNotNull(fooBean);
assertEquals(ApplicationScoped.class.getName(), fooBean.getString("scope"));
assertEquals("CLASS", fooBean.getString("kind"));
assertEquals("foo", fooBean.getString("name"));
String beanId = fooBean.getString("id");
assertNotNull(beanId);

for (int i = 0; i < observers.size(); i++) {
JsonObject observer = observers.getJsonObject(i);
if (beanId.equals(observer.getString("declaringBean"))) {
fooObserver = observer;
}
}
assertNotNull(fooObserver);
assertEquals(StartupEvent.class.getName(), fooObserver.getString("observedType"));
assertEquals(2500, fooObserver.getInteger("priority"));
}

@Named
@ApplicationScoped
public static class Foo {

void onStart(@Observes StartupEvent event) {
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.quarkus.vertx.http.runtime.devmode;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;

import javax.enterprise.inject.Any;
import javax.enterprise.inject.Default;

import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableObserverMethod;
import io.quarkus.arc.impl.ArcContainerImpl;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.devmode.Json.JsonArrayBuilder;
import io.quarkus.vertx.http.runtime.devmode.Json.JsonObjectBuilder;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

@Recorder
public class ArcEndpointRecorder {

public Handler<RoutingContext> createBeansHandler() {
return new Handler<RoutingContext>() {

@Override
public void handle(RoutingContext ctx) {
ctx.response().putHeader("Content-Type", "application/json");

ArcContainerImpl container = ArcContainerImpl.instance();
List<InjectableBean<?>> beans = container.getBeans();
beans.addAll(container.getInterceptors());

String kindParam = ctx.request().getParam("kind");
InjectableBean.Kind kind = kindParam != null ? InjectableBean.Kind.valueOf(kindParam) : null;
String scopeEndsWith = ctx.request().getParam("scope");
String beanClassStartsWith = ctx.request().getParam("beanClass");

for (Iterator<InjectableBean<?>> it = beans.iterator(); it.hasNext();) {
InjectableBean<?> injectableBean = it.next();
if (kind != null && !kind.equals(injectableBean.getKind())) {
it.remove();
}
if (scopeEndsWith != null && !injectableBean.getScope().getName().endsWith(scopeEndsWith)) {
it.remove();
}
if (beanClassStartsWith != null
&& !injectableBean.getBeanClass().getName().startsWith(beanClassStartsWith)) {
it.remove();
}
}

JsonArrayBuilder array = Json.array();
for (InjectableBean<?> injectableBean : beans) {
JsonObjectBuilder bean = Json.object();
bean.put("id", injectableBean.getIdentifier());
bean.put("kind", injectableBean.getKind().toString());
bean.put("generatedClass", injectableBean.getClass().getName());
bean.put("beanClass", injectableBean.getBeanClass().getName());
JsonArrayBuilder types = Json.array();
for (Type beanType : injectableBean.getTypes()) {
types.add(beanType.getTypeName());
}
bean.put("types", types);
JsonArrayBuilder qualifiers = Json.array();
for (Annotation qualifier : injectableBean.getQualifiers()) {
if (qualifier.annotationType().equals(Any.class) || qualifier.annotationType().equals(Default.class)) {
qualifiers.add("@" + qualifier.annotationType().getSimpleName());
} else {
qualifiers.add(qualifier.toString());
}
}
bean.put("qualifiers", qualifiers);
bean.put("scope", injectableBean.getScope().getName());

if (injectableBean.getDeclaringBean() != null) {
bean.put("declaringBean", injectableBean.getDeclaringBean().getIdentifier());
}
if (injectableBean.getName() != null) {
bean.put("name", injectableBean.getName());
}
if (injectableBean.isAlternative()) {
bean.put("alternativePriority", injectableBean.getAlternativePriority());
}
if (injectableBean.isDefaultBean()) {
bean.put("isDefault", true);
}
array.add(bean);
}
ctx.response().end(array.build());
}
};
}

public Handler<RoutingContext> createObserversHandler() {
return new Handler<RoutingContext>() {

@Override
public void handle(RoutingContext ctx) {
ctx.response().putHeader("Content-Type", "application/json");

ArcContainerImpl container = ArcContainerImpl.instance();
List<InjectableObserverMethod<?>> observers = container.getObservers();

JsonArrayBuilder array = Json.array();
for (InjectableObserverMethod<?> injectableObserver : observers) {
JsonObjectBuilder observer = Json.object();
observer.put("generatedClass", injectableObserver.getClass().getName());
observer.put("observedType", injectableObserver.getObservedType().getTypeName());
if (!injectableObserver.getObservedQualifiers().isEmpty()) {
JsonArrayBuilder qualifiers = Json.array();
for (Annotation qualifier : injectableObserver.getObservedQualifiers()) {
qualifiers.add(qualifier.toString());
}
observer.put("qualifiers", qualifiers);
}
observer.put("priority", injectableObserver.getPriority());
observer.put("reception", injectableObserver.getReception().toString());
observer.put("transactionPhase", injectableObserver.getTransactionPhase().toString());
observer.put("async", injectableObserver.isAsync());
if (injectableObserver.getDeclaringBeanIdentifier() != null) {
observer.put("declaringBean", injectableObserver.getDeclaringBeanIdentifier());
}
array.add(observer);
}
ctx.response().end(array.build());
}
};
}

}
Loading

0 comments on commit 9cac700

Please sign in to comment.