From 323edd9faedd1589710f639132cb0d79fa787c46 Mon Sep 17 00:00:00 2001 From: Michal Foksa Date: Sat, 7 May 2022 20:09:42 +0200 Subject: [PATCH 1/2] #310 New annotation @AvroNamespace to override Avro schema field namespace. Current namespace value is Java package name. This annotation allows to override its name. --- .../avro/annotations/AvroNamespace.java | 16 ++++ .../avro/schema/AvroSchemaHelper.java | 12 +-- .../avro/annotations/AvroNamespaceTest.java | 85 +++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespaceTest.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java new file mode 100644 index 000000000..f5e6959ad --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java @@ -0,0 +1,16 @@ +package com.fasterxml.jackson.dataformat.avro.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation allows to override default Avro type namespace value. + * Default value is Java package name. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AvroNamespace { + String value(); +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/AvroSchemaHelper.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/AvroSchemaHelper.java index 3dfba579f..b24e0583c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/AvroSchemaHelper.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/AvroSchemaHelper.java @@ -1,6 +1,5 @@ package com.fasterxml.jackson.dataformat.avro.schema; -import com.fasterxml.jackson.databind.util.LRUMap; import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; @@ -23,6 +22,8 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.LRUMap; +import com.fasterxml.jackson.dataformat.avro.annotations.AvroNamespace; public abstract class AvroSchemaHelper { @@ -97,8 +98,9 @@ public static boolean isStringable(AnnotatedClass type) { return false; } - protected static String getNamespace(JavaType type) { - return getNamespace(type.getRawClass()); + protected static String getNamespace(BeanDescription bean) { + AvroNamespace ann = bean.getClassInfo().getAnnotation(AvroNamespace.class); + return ann != null ? ann.value() : getNamespace(bean.getType().getRawClass()); } protected static String getNamespace(Class cls) { @@ -244,7 +246,7 @@ public static Schema initializeRecordSchema(BeanDescription bean) { return addAlias(Schema.createRecord( getName(bean.getType()), bean.findClassDescription(), - getNamespace(bean.getType()), + getNamespace(bean), bean.getType().isTypeOrSubTypeOf(Throwable.class) ), bean); } @@ -268,7 +270,7 @@ public static Schema createEnumSchema(BeanDescription bean, List values) return addAlias(Schema.createEnum( getName(bean.getType()), bean.findClassDescription(), - getNamespace(bean.getType()), values + getNamespace(bean), values ), bean); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespaceTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespaceTest.java new file mode 100644 index 000000000..52e9c7296 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespaceTest.java @@ -0,0 +1,85 @@ +package com.fasterxml.jackson.dataformat.avro.annotations; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; +import org.apache.avro.Schema; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AvroNamespaceTest { + + static class ClassWithoutAvroNamespaceAnnotation { + } + + @AvroNamespace("ClassWithAvroNamespaceAnnotation.namespace") + static class ClassWithAvroNamespaceAnnotation { + } + + enum EnumWithoutAvroNamespaceAnnotation {FOO, BAR;} + + @AvroNamespace("EnumWithAvroNamespaceAnnotation.namespace") + enum EnumWithAvroNamespaceAnnotation {FOO, BAR;} + + @Test + public void class_without_AvroNamespace_test() throws JsonMappingException { + // GIVEN + AvroMapper mapper = new AvroMapper(); + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + + // WHEN + mapper.acceptJsonFormatVisitor(ClassWithoutAvroNamespaceAnnotation.class, gen); + Schema actualSchema = gen.getGeneratedSchema().getAvroSchema(); + + // THEN + assertThat(actualSchema.getNamespace()) + .isEqualTo("com.fasterxml.jackson.dataformat.avro.annotations.AvroNamespaceTest$"); + } + + @Test + public void class_with_AvroNamespace_test() throws JsonMappingException { + // GIVEN + AvroMapper mapper = new AvroMapper(); + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + + // WHEN + mapper.acceptJsonFormatVisitor(ClassWithAvroNamespaceAnnotation.class, gen); + Schema actualSchema = gen.getGeneratedSchema().getAvroSchema(); + + // THEN + assertThat(actualSchema.getNamespace()) + .isEqualTo("ClassWithAvroNamespaceAnnotation.namespace"); + } + + @Test + public void enum_without_AvroNamespace_test() throws JsonMappingException { + // GIVEN + AvroMapper mapper = new AvroMapper(); + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + + // WHEN + mapper.acceptJsonFormatVisitor(EnumWithoutAvroNamespaceAnnotation.class, gen); + Schema actualSchema = gen.getGeneratedSchema().getAvroSchema(); + + // THEN + assertThat(actualSchema.getNamespace()) + .isEqualTo("com.fasterxml.jackson.dataformat.avro.annotations.AvroNamespaceTest$"); + } + + @Test + public void enum_with_AvroNamespace_test() throws JsonMappingException { + // GIVEN + AvroMapper mapper = new AvroMapper(); + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + + // WHEN + mapper.acceptJsonFormatVisitor(EnumWithAvroNamespaceAnnotation.class, gen); + Schema actualSchema = gen.getGeneratedSchema().getAvroSchema(); + + // THEN + assertThat(actualSchema.getNamespace()) + .isEqualTo("EnumWithAvroNamespaceAnnotation.namespace"); + } + +} From 4a9f7d0e8955d78cad73b231155277c0363eaf67 Mon Sep 17 00:00:00 2001 From: Michal Foksa Date: Mon, 9 May 2022 08:27:25 +0200 Subject: [PATCH 2/2] ElementType.ANNOTATION_TYPE added to allow "annotation bundle" usage. --- .../jackson/dataformat/avro/annotations/AvroNamespace.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java index f5e6959ad..75fb3ed74 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/annotations/AvroNamespace.java @@ -9,7 +9,7 @@ * Annotation allows to override default Avro type namespace value. * Default value is Java package name. */ -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface AvroNamespace { String value();