From 6b01e5a2adcfeed9b9c37416fe9be4ba29a03739 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 7 Dec 2022 11:56:53 +0200 Subject: [PATCH] Introduce a way to use custom annotations in bytecode recording --- .../recording/BytecodeRecorderImpl.java | 7 +++- .../RecordingAnnotationsProvider.java | 8 ++++ .../recording/RecordingAnnotationsUtil.java | 40 +++++++++++++++++++ .../recording/BytecodeRecorderTestCase.java | 3 +- .../recording/IgnoredProperties.java | 21 ++++++++-- .../TestRecordingAnnotationsProvider.java | 20 ++++++++++ ...ent.recording.RecordingAnnotationsProvider | 1 + .../src/main/asciidoc/writing-extensions.adoc | 7 ++++ 8 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsProvider.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsUtil.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecordingAnnotationsProvider.java create mode 100644 core/deployment/src/test/resources/META-INF/services/io.quarkus.deployment.recording.RecordingAnnotationsProvider diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index 971f22871795c..0de38d2d9940a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -1219,7 +1219,7 @@ public void prepare(MethodContext context) { for (Property i : desc) { if (!i.getDeclaringClass().getPackageName().startsWith("java.")) { // check if the getter is ignored - if ((i.getReadMethod() != null) && (i.getReadMethod().getAnnotation(IgnoreProperty.class) != null)) { + if ((i.getReadMethod() != null) && RecordingAnnotationsUtil.isIgnored(i.getReadMethod())) { continue; } // check if the matching field is ignored @@ -1556,7 +1556,10 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand * Returns {@code true} iff the field is annotated {@link IgnoreProperty} or the field is marked as {@code transient} */ private static boolean ignoreField(Field field) { - return (field.getAnnotation(IgnoreProperty.class) != null) || Modifier.isTransient(field.getModifiers()); + if (Modifier.isTransient(field.getModifiers())) { + return true; + } + return RecordingAnnotationsUtil.isIgnored(field); } private DeferredParameter findLoaded(final Object param) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsProvider.java new file mode 100644 index 0000000000000..6b544f2f4de1e --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsProvider.java @@ -0,0 +1,8 @@ +package io.quarkus.deployment.recording; + +import java.lang.annotation.Annotation; + +public interface RecordingAnnotationsProvider { + + Class ignoredProperty(); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsUtil.java new file mode 100644 index 0000000000000..b153e34b808dc --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingAnnotationsUtil.java @@ -0,0 +1,40 @@ +package io.quarkus.deployment.recording; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.util.HashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; + +import io.quarkus.runtime.annotations.IgnoreProperty; + +final class RecordingAnnotationsUtil { + + static final List> IGNORED_PROPERTY_ANNOTATIONS; + + static { + Set> ignoredPropertyAnnotations = new HashSet<>(); + ignoredPropertyAnnotations.add(IgnoreProperty.class); + for (RecordingAnnotationsProvider provider : ServiceLoader.load(RecordingAnnotationsProvider.class)) { + Class ignoredProperty = provider.ignoredProperty(); + if (ignoredProperty != null) { + ignoredPropertyAnnotations.add(ignoredProperty); + } + } + IGNORED_PROPERTY_ANNOTATIONS = List.copyOf(ignoredPropertyAnnotations); + } + + private RecordingAnnotationsUtil() { + } + + static boolean isIgnored(AccessibleObject object) { + for (int i = 0; i < IGNORED_PROPERTY_ANNOTATIONS.size(); i++) { + Class annotation = IGNORED_PROPERTY_ANNOTATIONS.get(i); + if (object.isAnnotationPresent(annotation)) { + return true; + } + } + return false; + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java index ea887b2d7463e..2600ca97414b7 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java @@ -167,8 +167,9 @@ public void testIgnoredProperties() throws Exception { ignoredProperties.setNotIgnored("Shows up"); ignoredProperties.setIgnoredField("Does not show up"); ignoredProperties.setAnotherIgnoredField("Does not show up either"); + ignoredProperties.setCustomIgnoredField("Does not show up either"); recorder.ignoredProperties(ignoredProperties); - }, new IgnoredProperties("Shows up", null, null)); + }, new IgnoredProperties("Shows up", null, null, null)); } @Test diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/IgnoredProperties.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/IgnoredProperties.java index 24a80a71c83c3..6f6aaafee4240 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/recording/IgnoredProperties.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/IgnoredProperties.java @@ -12,13 +12,17 @@ public class IgnoredProperties { private transient String anotherIgnoredField; + @TestRecordingAnnotationsProvider.TestIgnoreProperty + private String customIgnoredField; + public IgnoredProperties() { } - public IgnoredProperties(String notIgnored, String ignoredField, String anotherIgnoredField) { + public IgnoredProperties(String notIgnored, String ignoredField, String anotherIgnoredField, String customIgnoredField) { this.notIgnored = notIgnored; this.ignoredField = ignoredField; this.anotherIgnoredField = anotherIgnoredField; + this.customIgnoredField = customIgnoredField; } public String getNotIgnored() { @@ -45,6 +49,14 @@ public void setAnotherIgnoredField(String anotherIgnoredField) { this.anotherIgnoredField = anotherIgnoredField; } + public String getCustomIgnoredField() { + return customIgnoredField; + } + + public void setCustomIgnoredField(String customIgnoredField) { + this.customIgnoredField = customIgnoredField; + } + @IgnoreProperty public String getSomethingElse() { throw new IllegalStateException("This should not have been called"); @@ -59,12 +71,13 @@ public boolean equals(Object o) { IgnoredProperties that = (IgnoredProperties) o; return Objects.equals(notIgnored, that.notIgnored) && Objects.equals(ignoredField, that.ignoredField) && - Objects.equals(anotherIgnoredField, that.anotherIgnoredField); + Objects.equals(anotherIgnoredField, that.anotherIgnoredField) && + Objects.equals(customIgnoredField, that.customIgnoredField); } @Override public int hashCode() { - return Objects.hash(notIgnored, ignoredField, anotherIgnoredField); + return Objects.hash(notIgnored, ignoredField, anotherIgnoredField, customIgnoredField); } @Override @@ -72,6 +85,8 @@ public String toString() { return "IgnoredProperties{" + "notIgnored='" + notIgnored + '\'' + ", ignoredField='" + ignoredField + '\'' + + ", anotherIgnoredField='" + anotherIgnoredField + '\'' + + ", customIgnoredField='" + customIgnoredField + '\'' + '}'; } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecordingAnnotationsProvider.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecordingAnnotationsProvider.java new file mode 100644 index 0000000000000..a09698d944a24 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecordingAnnotationsProvider.java @@ -0,0 +1,20 @@ +package io.quarkus.deployment.recording; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class TestRecordingAnnotationsProvider implements RecordingAnnotationsProvider { + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.FIELD }) + public @interface TestIgnoreProperty { + } + + @Override + public Class ignoredProperty() { + return TestIgnoreProperty.class; + } +} diff --git a/core/deployment/src/test/resources/META-INF/services/io.quarkus.deployment.recording.RecordingAnnotationsProvider b/core/deployment/src/test/resources/META-INF/services/io.quarkus.deployment.recording.RecordingAnnotationsProvider new file mode 100644 index 0000000000000..3a18a34a6f991 --- /dev/null +++ b/core/deployment/src/test/resources/META-INF/services/io.quarkus.deployment.recording.RecordingAnnotationsProvider @@ -0,0 +1 @@ +io.quarkus.deployment.recording.TestRecordingAnnotationsProvider diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index f58249f52a563..8a1b36b4da976 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -1522,6 +1522,13 @@ The following objects can be passed to recorders: - Any arbitrary object via the `io.quarkus.deployment.recording.RecorderContext#registerSubstitution(Class, Class, Class)` mechanism - Arrays, Lists and Maps of the above +[NOTE] +==== +In cases where some fields of an object to be recorded should be ignored (i.e. the value that being at build time should not be reflected at runtime), the `@IgnoreProperty` can be placed on the field. + +If the class cannot depend on Quarkus, then Quarkus can use any custom annotation, as long as the extension implements the `io.quarkus.deployment.recording.RecordingAnnotationsProvider` SPI. +==== + ==== Injecting Configuration into Recorders Configuration objects with phase `RUNTIME` or `BUILD_AND_RUNTIME_FIXED` can be injected into recorders via constructor