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..75fb3ed74 --- /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, ElementType.ANNOTATION_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"); + } + +}