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 800e98c92..79f0e5dcc 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 @@ -9,6 +9,7 @@ import org.apache.avro.Schema; import org.apache.avro.Schema.Parser; +import org.apache.avro.reflect.AvroAlias; import org.apache.avro.reflect.Stringable; import org.apache.avro.specific.SpecificData; @@ -197,12 +198,12 @@ protected static T throwUnsupported() { * needs to have fields added to it. */ public static Schema initializeRecordSchema(BeanDescription bean) { - return Schema.createRecord( + return addAlias(Schema.createRecord( getName(bean.getType()), bean.findClassDescription(), getNamespace(bean.getType()), bean.getType().isTypeOrSubTypeOf(Throwable.class) - ); + ), bean); } /** @@ -221,7 +222,25 @@ public static Schema parseJsonSchema(String json) { * @return An {@link org.apache.avro.Schema.Type#ENUM ENUM} schema. */ public static Schema createEnumSchema(BeanDescription bean, List values) { - return Schema.createEnum(getName(bean.getType()), bean.findClassDescription(), getNamespace(bean.getType()), values); + return addAlias(Schema.createEnum( + getName(bean.getType()), + bean.findClassDescription(), + getNamespace(bean.getType()), values + ), bean); + } + + /** + * Looks for {@link AvroAlias @AvroAlias} on {@code bean} and adds it to {@code schema} if it exists + * @param schema Schema to which the alias should be added + * @param bean Bean to inspect for type aliases + * @return {@code schema}, possibly with an alias added + */ + public static Schema addAlias(Schema schema, BeanDescription bean) { + AvroAlias ann = bean.getClassInfo().getAnnotation(AvroAlias.class); + if (ann != null) { + schema.addAlias(ann.alias(), ann.space().equals(AvroAlias.NULL) ? null : ann.space()); + } + return schema; } /** diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/AvroTestBase.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/AvroTestBase.java index aa61d2448..d59312345 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/AvroTestBase.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/AvroTestBase.java @@ -8,9 +8,9 @@ import junit.framework.TestCase; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; public abstract class AvroTestBase extends TestCase { @@ -61,7 +61,7 @@ public abstract class AvroTestBase extends TestCase /********************************************************** */ - protected static class Employee + public static class Employee { public Employee() { } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/interop/annotations/AvroAliasTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/interop/annotations/AvroAliasTest.java new file mode 100644 index 000000000..72a1ae329 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/interop/annotations/AvroAliasTest.java @@ -0,0 +1,141 @@ +package com.fasterxml.jackson.dataformat.avro.interop.annotations; + +import java.io.IOException; + +import org.apache.avro.Schema; +import org.apache.avro.SchemaCompatibility; +import org.apache.avro.reflect.AvroAlias; +import org.apache.avro.reflect.Nullable; +import org.junit.Test; + +import com.fasterxml.jackson.dataformat.avro.AvroTestBase; +import com.fasterxml.jackson.dataformat.avro.interop.InteropTestBase; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AvroAliasTest extends InteropTestBase { + + @AvroAlias(alias = "Employee", space = "com.fasterxml.jackson.dataformat.avro.AvroTestBase$") + public static class NewEmployee { + + public String name; + + public int age; + + public String[] emails; + + public NewEmployee boss; + } + + @AvroAlias(alias = "NewEmployee") + public static class AliasedNameEmployee { + + public String name; + + public int age; + + public String[] emails; + + @Nullable + public AliasedNameEmployee boss; + } + + @AvroAlias(alias = "Size", space = "com.fasterxml.jackson.dataformat.avro.AvroTestBase$") + public static enum NewSize { + SMALL, + LARGE; + } + + @AvroAlias(alias = "NewestSize") + public static enum NewerSize { + SMALL, + LARGE; + } + + @AvroAlias(alias = "NewerSize") + public static enum NewestSize { + SMALL, + LARGE; + } + + @Test + public void testAliasedRecordForwardsCompatible() throws IOException { + Schema oldSchema = schemaFunctor.apply(AvroTestBase.Employee.class); + Schema newSchema = schemaFunctor.apply(NewEmployee.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(newSchema, oldSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.COMPATIBLE); + } + + @Test + public void testAliasedRecordBackwardsCompatible() throws IOException { + Schema oldSchema = schemaFunctor.apply(AvroTestBase.Employee.class); + Schema newSchema = schemaFunctor.apply(NewEmployee.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(oldSchema, newSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.INCOMPATIBLE); + } + + @Test + public void testAliasedRecordForwardsCompatibleSameNamespace() throws IOException { + Schema oldSchema = schemaFunctor.apply(NewEmployee.class); + Schema newSchema = schemaFunctor.apply(AliasedNameEmployee.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(newSchema, oldSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.COMPATIBLE); + } + + @Test + public void testAliasedRecordBackwardsCompatibleSameNamespace() throws IOException { + Schema oldSchema = schemaFunctor.apply(NewEmployee.class); + Schema newSchema = schemaFunctor.apply(AliasedNameEmployee.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(oldSchema, newSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.INCOMPATIBLE); + } + + @Test + public void testAliasedEnumForwardsCompatible() throws IOException { + Schema oldSchema = schemaFunctor.apply(AvroTestBase.Size.class); + Schema newSchema = schemaFunctor.apply(NewSize.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(newSchema, oldSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.COMPATIBLE); + } + + @Test + public void testAliasedEnumBackwardsCompatible() throws IOException { + Schema oldSchema = schemaFunctor.apply(AvroTestBase.Size.class); + Schema newSchema = schemaFunctor.apply(NewSize.class); + // + SchemaCompatibility.SchemaPairCompatibility compatibility = + SchemaCompatibility.checkReaderWriterCompatibility(oldSchema, newSchema); + // + assertThat(compatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.INCOMPATIBLE); + } + + @Test + public void testAliasedEnumForwardsAndBackwardsCompatible() throws IOException { + Schema oldSchema = schemaFunctor.apply(NewerSize.class); + Schema newSchema = schemaFunctor.apply(NewestSize.class); + // + SchemaCompatibility.SchemaPairCompatibility backwardsCompatibility = + SchemaCompatibility.checkReaderWriterCompatibility(oldSchema, newSchema); + SchemaCompatibility.SchemaPairCompatibility forwardsCompatibility = + SchemaCompatibility.checkReaderWriterCompatibility(newSchema, oldSchema); + // + assertThat(backwardsCompatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.COMPATIBLE); + assertThat(forwardsCompatibility.getType()).isEqualTo(SchemaCompatibility.SchemaCompatibilityType.COMPATIBLE); + } + +}