From 9cac7004945cb5986dee31c13dad95d830c83948 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 18 May 2020 15:07:40 +0200 Subject: [PATCH] ArC - register /arc/beans and /arc/observers routes in the dev mode - these routes produce basic debug info in the JSON format --- docs/src/main/asciidoc/cdi-reference.adoc | 15 + .../devmode/ArcEndpointProcessor.java | 20 + .../vertx/http/devmode/ArcEndpointTest.java | 66 ++++ .../runtime/devmode/ArcEndpointRecorder.java | 131 +++++++ .../vertx/http/runtime/devmode/Json.java | 344 ++++++++++++++++++ .../vertx/http/runtime/devmode/JsonTest.java | 49 +++ .../quarkus/arc/processor/BeanGenerator.java | 9 + .../arc/processor/ObserverGenerator.java | 9 + .../java/io/quarkus/arc/InjectableBean.java | 19 + .../io/quarkus/arc/InjectableInterceptor.java | 5 + .../quarkus/arc/InjectableObserverMethod.java | 7 + .../io/quarkus/arc/impl/ArcContainerImpl.java | 14 +- 12 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcEndpointProcessor.java create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/ArcEndpointTest.java create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ArcEndpointRecorder.java create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/Json.java create mode 100644 extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/devmode/JsonTest.java diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index c33d0ba3eeea1..b47c02b77e8ac 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -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 diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcEndpointProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcEndpointProcessor.java new file mode 100644 index 0000000000000..d86bdb9903c7c --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcEndpointProcessor.java @@ -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 routes) { + routes.produce(new RouteBuildItem("/arc/beans", recorder.createBeansHandler())); + routes.produce(new RouteBuildItem("/arc/observers", recorder.createObserversHandler())); + } + +} diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/ArcEndpointTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/ArcEndpointTest.java new file mode 100644 index 0000000000000..90d1754d8650f --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/ArcEndpointTest.java @@ -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) { + } + + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ArcEndpointRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ArcEndpointRecorder.java new file mode 100644 index 0000000000000..ad0c4c5dec82c --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ArcEndpointRecorder.java @@ -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 createBeansHandler() { + return new Handler() { + + @Override + public void handle(RoutingContext ctx) { + ctx.response().putHeader("Content-Type", "application/json"); + + ArcContainerImpl container = ArcContainerImpl.instance(); + List> 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> 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 createObserversHandler() { + return new Handler() { + + @Override + public void handle(RoutingContext ctx) { + ctx.response().putHeader("Content-Type", "application/json"); + + ArcContainerImpl container = ArcContainerImpl.instance(); + List> 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()); + } + }; + } + +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/Json.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/Json.java new file mode 100644 index 0000000000000..bd1c6ad41746e --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/Json.java @@ -0,0 +1,344 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +/** + * A simple JSON string generator. + */ +public final class Json { + + private static final String OBJECT_START = "{"; + private static final String OBJECT_END = "}"; + private static final String ARRAY_START = "["; + private static final String ARRAY_END = "]"; + private static final String NAME_VAL_SEPARATOR = ":"; + private static final String ENTRY_SEPARATOR = ","; + + private static final int CONTROL_CHAR_START = 0; + private static final int CONTROL_CHAR_END = 0x1f; + private static final char CHAR_QUOTATION_MARK = '"'; + + private static final Map REPLACEMENTS; + + static { + REPLACEMENTS = new HashMap<>(); + // control characters + for (int i = CONTROL_CHAR_START; i <= CONTROL_CHAR_END; i++) { + REPLACEMENTS.put((char) i, String.format("\\u%04x", i)); + } + // quotation mark + REPLACEMENTS.put('"', "\\\""); + // reverse solidus + REPLACEMENTS.put('\\', "\\\\"); + } + + private Json() { + } + + /** + * @return the new JSON array builder, empty builders are not ignored + */ + public static JsonArrayBuilder array() { + return new JsonArrayBuilder(false); + } + + /** + * + * @param ignoreEmptyBuilders + * @return the new JSON array builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonArrayBuilder array(boolean ignoreEmptyBuilders) { + return new JsonArrayBuilder(ignoreEmptyBuilders); + } + + /** + * + * @return the new JSON object builder, empty builders are not ignored + */ + public static JsonObjectBuilder object() { + return new JsonObjectBuilder(false); + } + + /** + * + * @param ignoreEmptyBuilders + * @return the new JSON object builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonObjectBuilder object(boolean ignoreEmptyBuilders) { + return new JsonObjectBuilder(ignoreEmptyBuilders); + } + + abstract static class JsonBuilder { + + protected boolean ignoreEmptyBuilders = false; + + /** + * + * @param ignoreEmptyBuilders If set to true all empty builders added to this builder will be ignored during + * {@link #build()} + */ + JsonBuilder(boolean ignoreEmptyBuilders) { + this.ignoreEmptyBuilders = ignoreEmptyBuilders; + } + + /** + * + * @return true if there are no elements/properties, false otherwise + */ + public abstract boolean isEmpty(); + + /** + * + * @return a string representation + */ + public abstract String build(); + + /** + * + * @param value + * @return true if the value is null or an empty builder and {@link #ignoreEmptyBuilders} is set to + * true, false + * otherwise + */ + protected boolean isIgnored(Object value) { + return value == null || (ignoreEmptyBuilders && value instanceof JsonBuilder && ((JsonBuilder) value).isEmpty()); + } + + protected boolean isValuesEmpty(Collection values) { + if (values.isEmpty()) { + return true; + } + for (Object object : values) { + if (object instanceof JsonBuilder) { + if (!((JsonBuilder) object).isEmpty()) { + return false; + } + } else { + return false; + } + } + return true; + + } + + protected abstract T self(); + + } + + /** + * JSON array builder. + */ + public static class JsonArrayBuilder extends JsonBuilder { + + private final List values; + + private JsonArrayBuilder(boolean ignoreEmptyBuilders) { + super(ignoreEmptyBuilders); + this.values = new ArrayList(); + } + + public JsonArrayBuilder add(JsonArrayBuilder value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(JsonObjectBuilder value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(String value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(Boolean value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(Integer value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(Long value) { + addInternal(value); + return this; + } + + private void addInternal(Object value) { + if (value != null) { + values.add(value); + } + } + + public boolean isEmpty() { + return isValuesEmpty(values); + } + + public String build() { + StringBuilder builder = new StringBuilder(); + builder.append(ARRAY_START); + int idx = 0; + for (ListIterator iterator = values.listIterator(); iterator.hasNext();) { + Object value = iterator.next(); + if (isIgnored(value)) { + continue; + } + if (++idx > 1) { + builder.append(ENTRY_SEPARATOR); + } + appendValue(builder, value); + } + builder.append(ARRAY_END); + return builder.toString(); + } + + @Override + protected JsonArrayBuilder self() { + return this; + } + + } + + /** + * JSON object builder. + */ + public static class JsonObjectBuilder extends JsonBuilder { + + private final Map properties; + + private JsonObjectBuilder(boolean ignoreEmptyBuilders) { + super(ignoreEmptyBuilders); + this.properties = new LinkedHashMap(); + } + + public JsonObjectBuilder put(String name, String value) { + addInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, JsonObjectBuilder value) { + addInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, JsonArrayBuilder value) { + addInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, Boolean value) { + addInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, Integer value) { + addInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, Long value) { + addInternal(name, value); + return this; + } + + public boolean has(String name) { + return properties.containsKey(name); + } + + private void addInternal(String name, Object value) { + Objects.requireNonNull(name); + if (value != null) { + properties.put(name, value); + } + } + + public boolean isEmpty() { + if (properties.isEmpty()) { + return true; + } + return isValuesEmpty(properties.values()); + } + + public String build() { + StringBuilder builder = new StringBuilder(); + builder.append(OBJECT_START); + int idx = 0; + for (Iterator> iterator = properties.entrySet().iterator(); iterator.hasNext();) { + Entry entry = iterator.next(); + if (isIgnored(entry.getValue())) { + continue; + } + if (++idx > 1) { + builder.append(ENTRY_SEPARATOR); + } + appendStringValue(builder, entry.getKey()); + builder.append(NAME_VAL_SEPARATOR); + appendValue(builder, entry.getValue()); + } + builder.append(OBJECT_END); + return builder.toString(); + } + + @Override + protected JsonObjectBuilder self() { + return this; + } + + } + + static void appendValue(StringBuilder builder, Object value) { + if (value instanceof JsonObjectBuilder) { + builder.append(((JsonObjectBuilder) value).build()); + } else if (value instanceof JsonArrayBuilder) { + builder.append(((JsonArrayBuilder) value).build()); + } else if (value instanceof String) { + appendStringValue(builder, value.toString()); + } else if (value instanceof Boolean || value instanceof Integer || value instanceof Long) { + builder.append(value.toString()); + } else { + throw new IllegalStateException("Unsupported value type: " + value); + } + } + + static void appendStringValue(StringBuilder builder, String value) { + builder.append(CHAR_QUOTATION_MARK); + builder.append(escape(value)); + builder.append(CHAR_QUOTATION_MARK); + } + + /** + * Escape quotation mark, reverse solidus and control characters (U+0000 through U+001F). + * + * @param value + * @return escaped value + * @see http://www.ietf.org/rfc/rfc4627.txt + */ + static String escape(String value) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + String replacement = REPLACEMENTS.get(c); + if (replacement != null) { + builder.append(replacement); + } else { + builder.append(c); + } + } + return builder.toString(); + } + +} diff --git a/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/devmode/JsonTest.java b/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/devmode/JsonTest.java new file mode 100644 index 0000000000000..9d83d1c7fd5ec --- /dev/null +++ b/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/devmode/JsonTest.java @@ -0,0 +1,49 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; + +import io.quarkus.vertx.http.runtime.devmode.Json.JsonArrayBuilder; +import io.quarkus.vertx.http.runtime.devmode.Json.JsonObjectBuilder; + +public class JsonTest { + + @Test + public void testJsonArray() { + assertEquals("[\"foo\",\"bar\",[\"baz\"]]", Json.array().add("foo").add("bar").add(Json.array().add("baz")).build()); + } + + @Test + public void testJsonObject() { + assertEquals("{\"foo\":\"bar\",\"baz\":[\"qux\"]}", + Json.object().put("foo", "bar").put("baz", Json.array().add("qux")).build()); + } + + @Test + public void testIgnoreEmptyBuilders() { + assertEquals("[true]", Json.array(true).add(true).add(Json.object(true).put("foo", Json.object())).build()); + JsonObjectBuilder objectBuilder = Json.object(); + JsonArrayBuilder arrayBuilder = Json.array().add(objectBuilder); + objectBuilder.put("foo", "bar"); + assertEquals("[{\"foo\":\"bar\"}]", arrayBuilder.build()); + } + + @Test + public void testABitMoreComplexStructure() { + JsonObjectBuilder builder = Json.object().put("items", Json.array().add(1).add(2)).put("name", "Foo").put("parent", + Json.object(true).put("name", "Martin").put("age", 100).put("active", true).put("children", + Json.array(true).add(Json.object()))); + assertFalse(builder.isEmpty()); + assertEquals("{\"items\":[1,2],\"name\":\"Foo\",\"parent\":{\"name\":\"Martin\",\"age\":100,\"active\":true}}", + builder.build()); + } + + @Test + public void testEscaping() { + assertEquals("{\"foo\":\"bar=\\\"baz\\u000a and \\u0009 F\\\"\"}", + Json.object().put("foo", "bar=\"baz\n and \t F\"").build()); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 5f98b62e30f5d..a513ca610b696 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -243,6 +243,7 @@ Collection generateSyntheticBean(BeanInfo bean) { if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); } + implementGetKind(beanCreator, InjectableBean.Kind.SYNTHETIC); beanCreator.close(); return classOutput.getResources(); @@ -425,6 +426,7 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); } + implementGetKind(beanCreator, InjectableBean.Kind.PRODUCER_METHOD); beanCreator.close(); return classOutput.getResources(); @@ -508,6 +510,7 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); } + implementGetKind(beanCreator, InjectableBean.Kind.PRODUCER_FIELD); beanCreator.close(); return classOutput.getResources(); @@ -1625,6 +1628,12 @@ protected void implementGetName(BeanInfo bean, ClassCreator beanCreator) { } } + protected void implementGetKind(ClassCreator beanCreator, InjectableBean.Kind kind) { + MethodCreator getScope = beanCreator.getMethodCreator("getKind", InjectableBean.Kind.class).setModifiers(ACC_PUBLIC); + getScope.returnValue(getScope + .readStaticField(FieldDescriptor.of(InjectableBean.Kind.class, kind.toString(), InjectableBean.Kind.class))); + } + protected void implementSupplierGet(ClassCreator beanCreator) { MethodCreator get = beanCreator.getMethodCreator("get", Object.class).setModifiers(ACC_PUBLIC); get.returnValue(get.getThis()); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java index dde27cdfb1cca..ff21d1ffb9ec1 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java @@ -180,6 +180,7 @@ Collection generate(ObserverInfo observer) { if (observer.isAsync()) { implementIsAsync(observerCreator); } + implementGetDeclaringBeanIdentifier(observerCreator, observer.getDeclaringBean()); observerCreator.close(); return classOutput.getResources(); @@ -233,6 +234,14 @@ protected void implementIsAsync(ClassCreator observerCreator) { isAsync.returnValue(isAsync.load(true)); } + protected void implementGetDeclaringBeanIdentifier(ClassCreator observerCreator, BeanInfo declaringBean) { + MethodCreator getDeclaringBeanIdentifier = observerCreator.getMethodCreator("getDeclaringBeanIdentifier", String.class) + .setModifiers(ACC_PUBLIC); + getDeclaringBeanIdentifier + .returnValue(declaringBean != null ? getDeclaringBeanIdentifier.load(declaringBean.getIdentifier()) + : getDeclaringBeanIdentifier.loadNull()); + } + protected void implementNotify(ObserverInfo observer, ClassCreator observerCreator, Map injectionPointToProviderField, ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java index 3ae17d551ff61..d234e0fe4226d 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java @@ -27,6 +27,15 @@ public interface InjectableBean extends Bean, InjectableReferenceProvider< */ String getIdentifier(); + /** + * + * @return the kind of the bean + * @see Kind + */ + default Kind getKind() { + return Kind.CLASS; + } + /** * * @return the scope @@ -105,4 +114,14 @@ default boolean isDefaultBean() { return false; } + enum Kind { + + CLASS, + PRODUCER_FIELD, + PRODUCER_METHOD, + SYNTHETIC, + INTERCEPTOR + + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java index 50e27fb08677b..8b17f5b850604 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java @@ -13,4 +13,9 @@ */ public interface InjectableInterceptor extends InjectableBean, Interceptor, Prioritized { + @Override + default Kind getKind() { + return Kind.INTERCEPTOR; + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java index 721599faf5bcd..f0bd934bf910c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java @@ -31,6 +31,13 @@ default TransactionPhase getTransactionPhase() { return TransactionPhase.IN_PROGRESS; } + /** + * + * @return the identifier or null for synthetic observers + * @see InjectableBean#getIdentifier() + */ + String getDeclaringBeanIdentifier(); + static int compare(InjectableObserverMethod o1, InjectableObserverMethod o2) { return Integer.compare(o1.getPriority(), o2.getPriority()); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 1f8f451739428..4966b67c9567c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -349,6 +349,18 @@ public synchronized void shutdown() { } } + public List> getBeans() { + return new ArrayList<>(beans); + } + + public List> getInterceptors() { + return new ArrayList<>(interceptors); + } + + public List> getObservers() { + return new ArrayList<>(observers); + } + InstanceHandle getResource(Type type, Set annotations) { for (ResourceReferenceProvider resourceProvider : resourceProviders) { InstanceHandle ret = resourceProvider.get(type, annotations); @@ -664,7 +676,7 @@ static ArcContainerImpl unwrap(ArcContainer container) { } } - static ArcContainerImpl instance() { + public static ArcContainerImpl instance() { return unwrap(Arc.container()); }