From 6a3aa4c6b92101a57a43d495532fd15be2707d96 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 8 May 2018 15:54:54 -0500 Subject: [PATCH 01/25] WIP Generation of avro schemas with logical types. --- .../jackson/dataformat/avro/AvroDate.java | 18 +++ .../jackson/dataformat/avro/AvroDecimal.java | 27 ++++ .../dataformat/avro/AvroTimeMicrosecond.java | 18 +++ .../dataformat/avro/AvroTimeMillisecond.java | 18 +++ .../avro/AvroTimestampMicrosecond.java | 18 +++ .../avro/AvroTimestampMillisecond.java | 18 +++ .../dataformat/avro/schema/RecordVisitor.java | 81 +++++++++--- .../avro/schema/TestLogicalTypes.java | 118 ++++++++++++++++++ 8 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java new file mode 100644 index 000000000..0632781c1 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroDate { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java new file mode 100644 index 000000000..379c7c9ed --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroDecimal { + /** + * The maximum precision of decimals stored in this type. + */ + int precision(); + + /** + * + * @return + */ + int scale() default 0; +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java new file mode 100644 index 000000000..aa346d038 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimeMicrosecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java new file mode 100644 index 000000000..e084f050b --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimeMillisecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java new file mode 100644 index 000000000..098391ebe --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimestampMicrosecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java new file mode 100644 index 000000000..414568241 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

+ * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimestampMillisecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index f1d6867bb..3f28cb5ac 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -5,6 +5,13 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; import org.apache.avro.reflect.AvroMeta; @@ -156,27 +163,65 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) if (fixedSize != null) { writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); } else { - JsonSerializer ser = null; + AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); + if(decimal!= null) { + LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); + writerSchema = d + .addToSchema(Schema.create(Type.BYTES)); + d.validate(writerSchema); + } else { + AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); + if(timestampMillisecond != null) { + writerSchema = LogicalTypes.timestampMillis() + .addToSchema(Schema.create(Type.LONG)); + } else { + AvroTimestampMicrosecond timestampMicrosecond = prop.getAnnotation(AvroTimestampMicrosecond.class); + if(timestampMicrosecond!=null){ + writerSchema = LogicalTypes.timestampMicros() + .addToSchema(Schema.create(Type.LONG)); + } else { + AvroDate date = prop.getAnnotation(AvroDate.class); + if(date!=null){ + writerSchema = LogicalTypes.date() + .addToSchema(Schema.create(Type.INT)); + } else { + AvroTimeMillisecond timeMillisecond = prop.getAnnotation(AvroTimeMillisecond.class); + if(timeMillisecond!=null) { + writerSchema = LogicalTypes.timeMillis() + .addToSchema(Schema.create(Type.INT)); + } else { + AvroTimeMicrosecond timeMicrosecond = prop.getAnnotation(AvroTimeMicrosecond.class); + if(timeMicrosecond!=null) { + writerSchema = LogicalTypes.timeMicros() + .addToSchema(Schema.create(Type.LONG)); + } else { + JsonSerializer ser = null; - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + } + ser = prov.findValueSerializer(prop.getType(), prop); + } + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); + } + } + } + } } - ser = prov.findValueSerializer(prop.getType(), prop); } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); } /* 23-Nov-2012, tatu: Actually let's also assume that primitive type values diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java new file mode 100644 index 000000000..71f3eba56 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -0,0 +1,118 @@ +package com.fasterxml.jackson.dataformat.avro.schema; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTestBase; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.Schema; + +import java.math.BigDecimal; +import java.util.Date; + +public class TestLogicalTypes extends AvroTestBase { + + static class DecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5) + public BigDecimal value; + } + + static class TimestampMillisecondsType { + @AvroTimestampMillisecond + @JsonProperty(required = true) + public Date value; + } + + static class TimeMillisecondsType { + @AvroTimeMillisecond + @JsonProperty(required = true) + public Date value; + } + + static class TimestampMicrosecondsType { + @AvroTimestampMicrosecond + @JsonProperty(required = true) + public Date value; + } + + static class TimeMicrosecondsType { + @AvroTimeMicrosecond + @JsonProperty(required = true) + public Date value; + } + + static class DateType { + @AvroDate + @JsonProperty(required = true) + public Date value; + } + + AvroSchema getSchema(Class cls) throws JsonMappingException { + AvroMapper avroMapper = new AvroMapper(); + AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); + avroMapper.acceptJsonFormatVisitor(cls, avroSchemaGenerator); + AvroSchema schema = avroSchemaGenerator.getGeneratedSchema(); + assertNotNull("Schema should not be null.", schema); + assertEquals(Schema.Type.RECORD, schema.getAvroSchema().getType()); + System.out.println(schema.getAvroSchema().toString(true)); + return schema; + } + + void assertLogicalType(Schema.Field field, final Schema.Type type, final String logicalType) { + assertEquals("schema().getType() does not match.", type, field.schema().getType()); + assertNotNull("logicalType should not be null.", field.schema().getLogicalType()); + assertEquals("logicalType does not match.", logicalType, field.schema().getLogicalType().getName()); + field.schema().getLogicalType().validate(field.schema()); + } + + public void testDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(DecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.BYTES, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + } + + public void testTimestampMillisecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimestampMillisecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "timestamp-millis"); + } + + public void testTimeMillisecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimeMillisecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.INT, "time-millis"); + } + + public void testTimestampMicrosecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimestampMicrosecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "timestamp-micros"); + } + + public void testTimeMicrosecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimeMicrosecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "time-micros"); + } + + public void testDateType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(DateType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.INT, "date"); + } +} From d21e6f0d6035cb74d88df6a11101433c15137c6c Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 15 May 2018 17:03:42 -0500 Subject: [PATCH 02/25] Decimal schema generation is working. Serialization should be working as well. --- .../jackson/dataformat/avro/AvroDecimal.java | 21 ++++++ .../dataformat/avro/schema/RecordVisitor.java | 15 ++++- .../avro/ser/NonBSGenericDatumWriter.java | 67 +++++++++++++++++-- .../avro/logicaltypes/DecimalTest.java | 53 +++++++++++++++ .../avro/schema/TestLogicalTypes.java | 43 +++++++++++- 5 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java index 379c7c9ed..d651ac9f8 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.dataformat.avro; +import org.apache.avro.Schema; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,6 +16,25 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface AvroDecimal { + /** + * + */ + Schema.Type schemaType() default Schema.Type.BYTES; + + /** + * The name of the type in the generated schema + */ + String typeName() default ""; + + /** + * The namespace of the type in the generated schema (optional) + */ + String typeNamespace() default ""; + + /** + * The size when the schemaType is FIXED. + */ + int fixedSize() default 0; /** * The maximum precision of decimals stored in this type. */ diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index 3f28cb5ac..eb3e9b102 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -166,8 +166,19 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); if(decimal!= null) { LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); - writerSchema = d - .addToSchema(Schema.create(Type.BYTES)); + Schema s; + if(Type.BYTES == decimal.schemaType()) { + s = Schema.create(Type.BYTES); + } else if(Type.FIXED == decimal.schemaType()) { + s = Schema.createFixed(decimal.typeName(), + null, + decimal.typeNamespace(), + decimal.fixedSize() + ); + } else { + throw new IllegalStateException("Avro schema type must be BYTES or FIXED."); + } + writerSchema = d.addToSchema(s); d.validate(writerSchema); } else { AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index f7b87a99c..d7102748d 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -4,9 +4,14 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.apache.avro.Conversion; +import org.apache.avro.Conversions; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; +import org.apache.avro.data.TimeConversions; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.io.Encoder; @@ -20,14 +25,62 @@ public class NonBSGenericDatumWriter extends GenericDatumWriter { - private static final GenericData GENERIC_DATA = GenericData.get(); - private final static Class CLS_STRING = String.class; private final static Class CLS_BIG_DECIMAL = BigDecimal.class; private final static Class CLS_BIG_INTEGER = BigInteger.class; - + + private final GenericData genericData; + public NonBSGenericDatumWriter(Schema root) { - super(root); + super(root); + genericData = GenericData.get(); + + Map> conversions = new HashMap<>(); + if(Type.RECORD == root.getType()) { + for(Schema.Field field:root.getFields()) { + Schema fieldSchema; + + if(Type.UNION == field.schema().getType()) { + fieldSchema = AvroWriteContext.resolveUnionType(field.schema(), null); + } else { + fieldSchema = field.schema(); + } + if(null==fieldSchema.getLogicalType()) { + continue; + } + String logicalTypeName = fieldSchema.getLogicalType().getName(); + if(conversions.containsKey(logicalTypeName)){ + continue; + } + switch (logicalTypeName) { + case "decimal": + conversions.put(logicalTypeName, new Conversions.DecimalConversion()); + break; + case "date": + conversions.put(logicalTypeName, new TimeConversions.DateConversion()); + break; + case "time-millis": + conversions.put(logicalTypeName, new TimeConversions.TimeConversion()); + break; + case "time-micros": + conversions.put(logicalTypeName, new TimeConversions.TimeMicrosConversion()); + break; + case "timestamp-millis": + conversions.put(logicalTypeName, new TimeConversions.TimestampConversion()); + break; + case "timestamp-micros": + conversions.put(logicalTypeName, new TimeConversions.TimestampMicrosConversion()); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported logical type.", logicalTypeName) + ); + } + } + for(Conversion conversion: conversions.values()) { + genericData.addLogicalTypeConversion(conversion); + } + } } @Override @@ -55,7 +108,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio } break; case ENUM: - super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); + super.writeWithoutConversion(schema, genericData.createEnum(datum.toString(), schema), out); return; case INT: if (datum.getClass() == CLS_STRING) { @@ -98,7 +151,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio ((EncodedDatum) datum).write(out); return; } - super.writeWithoutConversion(schema, datum, out); -// super.write(schema, datum, out); +// super.writeWithoutConversion(schema, datum, out); + super.write(schema, datum, out); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java new file mode 100644 index 000000000..b7e56650b --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import junit.framework.TestCase; + +import java.io.IOException; +import java.math.BigDecimal; + +public class DecimalTest extends TestCase { + static class RequiredDecimal { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + } + + static class OptionalDecimal { + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + } + + public void testRequired() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final RequiredDecimal expected = new RequiredDecimal(); + expected.value = BigDecimal.valueOf(123456, 3); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final RequiredDecimal actual = mapper.reader(avroSchema).forType(RequiredDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testOptional() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(OptionalDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final OptionalDecimal expected = new OptionalDecimal(); + expected.value = BigDecimal.valueOf(123456, 3); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalDecimal actual = mapper.reader(avroSchema).forType(OptionalDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 71f3eba56..14ebd663d 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -12,18 +12,32 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import org.apache.avro.Schema; +import org.apache.avro.SchemaParseException; +import org.junit.Assert; import java.math.BigDecimal; import java.util.Date; public class TestLogicalTypes extends AvroTestBase { - static class DecimalType { + static class BytesDecimalType { @JsonProperty(required = true) @AvroDecimal(precision = 5) public BigDecimal value; } + static class FixedNoNameDecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED) + public BigDecimal value; + } + + static class FixedDecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED, typeName = "foo", typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", fixedSize = 8) + public BigDecimal value; + } + static class TimestampMillisecondsType { @AvroTimestampMillisecond @JsonProperty(required = true) @@ -72,8 +86,22 @@ void assertLogicalType(Schema.Field field, final Schema.Type type, final String field.schema().getLogicalType().validate(field.schema()); } - public void testDecimalType() throws JsonMappingException { - AvroSchema avroSchema = getSchema(DecimalType.class); + public void testFixedNoNameDecimalType() throws JsonMappingException { + try { + AvroSchema avroSchema = getSchema(FixedNoNameDecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.BYTES, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + Assert.fail("SchemaParseException should have been thrown"); + } catch (SchemaParseException ex) { + + } + } + + public void testBytesDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(BytesDecimalType.class); Schema schema = avroSchema.getAvroSchema(); Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.BYTES, "decimal"); @@ -81,6 +109,15 @@ public void testDecimalType() throws JsonMappingException { assertEquals(0, field.schema().getObjectProp("scale")); } + public void testFixedDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(FixedDecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.FIXED, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + } + public void testTimestampMillisecondsType() throws JsonMappingException { AvroSchema avroSchema = getSchema(TimestampMillisecondsType.class); Schema schema = avroSchema.getAvroSchema(); From d316fa58daa2a6dc03effb88144a946784d57b1b Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Mon, 21 May 2018 17:57:09 -0500 Subject: [PATCH 03/25] Working using the JacksonAvroParserImpl. Both fixed and byte based decimals are working. --- .../avro/apacheimpl/ApacheAvroParserImpl.java | 20 ++++ .../dataformat/avro/deser/AvroParserImpl.java | 11 +++ .../avro/deser/AvroReaderFactory.java | 18 +++- .../avro/deser/JacksonAvroParserImpl.java | 28 ++++++ .../dataformat/avro/deser/ScalarDecoder.java | 97 ++++++++++++++++++ .../avro/ser/NonBSGenericDatumWriter.java | 99 +++++++------------ .../avro/logicaltypes/DecimalTest.java | 72 +++++++++++--- 7 files changed, 268 insertions(+), 77 deletions(-) diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java index 97af3aed4..0f187e97a 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java @@ -372,6 +372,26 @@ public int decodeEnum() throws IOException { return (_enumIndex = _decoder.readEnum()); } + @Override + public JsonToken decodeBytesDecimal(int scale) throws IOException { + return null; + } + + @Override + public void skipBytesDecimal() throws IOException { + + } + + @Override + public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { + return null; + } + + @Override + public void skipFixedDecimal(int size) throws IOException { + + } + /* /********************************************************** /* Methods for AvroReadContext impls, other diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java index 67b2d7aa3..39340140d 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java @@ -562,6 +562,17 @@ public long getRemainingElements() public abstract int decodeIndex() throws IOException; public abstract int decodeEnum() throws IOException; + /* + /********************************************************** + /* Methods for AvroReadContext implementations: decimals + /********************************************************** + */ + + public abstract JsonToken decodeBytesDecimal(int scale) throws IOException; + public abstract void skipBytesDecimal() throws IOException; + public abstract JsonToken decodeFixedDecimal(int scale, int size) throws IOException; + public abstract void skipFixedDecimal(int size) throws IOException; + /* /********************************************************** /* Methods for AvroReadContext impls, other diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java index 517e7fc2c..50b21176c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java @@ -2,6 +2,7 @@ import java.util.*; +import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.util.internal.JacksonUtils; @@ -56,13 +57,26 @@ public ScalarDecoder createScalarValueDecoder(Schema type) switch (type.getType()) { case BOOLEAN: return READER_BOOLEAN; - case BYTES: + case BYTES: + if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { + LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); + return new BytesDecimalReader( + decimal.getScale() + ); + } return READER_BYTES; case DOUBLE: return READER_DOUBLE; case ENUM: return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); - case FIXED: + case FIXED: + if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { + LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); + return new FixedDecimalReader( + decimal.getScale(), + type.getFixedSize() + ); + } return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); case FLOAT: return READER_FLOAT; diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java index 6103aea12..6058fb278 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java @@ -4,6 +4,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.io.IOContext; @@ -992,6 +994,32 @@ public int decodeEnum() throws IOException { return (_enumIndex = decodeInt()); } + @Override + public JsonToken decodeBytesDecimal(int scale) throws IOException { + decodeBytes(); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; + } + + @Override + public void skipBytesDecimal() throws IOException { + skipBytes(); + } + + @Override + public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { + decodeFixed(size); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; + } + + @Override + public void skipFixedDecimal(int size) throws IOException { + skipFixed(size); + } + @Override public boolean checkInputEnd() throws IOException { if (_closed) { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java index 55e67bd93..d8516fd15 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java @@ -1,6 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.deser; import java.io.IOException; +import java.math.BigDecimal; import java.util.List; import com.fasterxml.jackson.core.JsonToken; @@ -546,4 +547,100 @@ public void skipValue(AvroParserImpl parser) throws IOException { } } } + + protected final static class FixedDecimalReader extends ScalarDecoder { + private final int _scale; + private final int _size; + + public FixedDecimalReader(int scale, int size) { + _scale = scale; + _size = size; + } + + @Override + public JsonToken decodeValue(AvroParserImpl parser) throws IOException { + return parser.decodeFixedDecimal(_scale, _size); + } + + @Override + protected void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFixedDecimal(_size); + } + + @Override + public String getTypeId() { + return AvroSchemaHelper.getTypeId(BigDecimal.class); + } + + @Override + public AvroFieldReader asFieldReader(String name, boolean skipper) { + return new FR(name, skipper, getTypeId(), _scale, _size); + } + + private final static class FR extends AvroFieldReader { + private final int _scale; + private final int _size; + public FR(String name, boolean skipper, String typeId, int scale, int size) { + super(name, skipper, typeId); + _scale = scale; + _size = size; + } + + @Override + public JsonToken readValue(AvroReadContext parent, AvroParserImpl parser) throws IOException { + return parser.decodeFixedDecimal(_scale, _size); + } + + @Override + public void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFixedDecimal(_size); + } + } + } + + protected final static class BytesDecimalReader extends ScalarDecoder { + private final int _scale; + + public BytesDecimalReader(int scale) { + _scale = scale; + } + + @Override + public JsonToken decodeValue(AvroParserImpl parser) throws IOException { + return parser.decodeBytesDecimal(_scale); + } + + @Override + protected void skipValue(AvroParserImpl parser) throws IOException { + parser.skipBytesDecimal(); + } + + @Override + public String getTypeId() { + return AvroSchemaHelper.getTypeId(BigDecimal.class); + } + + @Override + public AvroFieldReader asFieldReader(String name, boolean skipper) { + return new FR(name, skipper, getTypeId(), _scale); + } + + private final static class FR extends AvroFieldReader { + private final int _scale; + public FR(String name, boolean skipper, String typeId, int scale) { + super(name, skipper, typeId); + _scale = scale; + } + + @Override + public JsonToken readValue(AvroReadContext parent, AvroParserImpl parser) throws IOException { + return parser.decodeBytesDecimal(_scale); + } + + @Override + public void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFloat(); + } + } + } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index d7102748d..90e3156aa 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -1,86 +1,36 @@ package com.fasterxml.jackson.dataformat.avro.ser; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.apache.avro.Conversion; import org.apache.avro.Conversions; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; -import org.apache.avro.data.TimeConversions; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.io.Encoder; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; + /** * Need to sub-class to prevent encoder from crapping on writing an optional * Enum value (see [dataformat-avro#12]) - * + * * @since 2.5 */ public class NonBSGenericDatumWriter extends GenericDatumWriter { + private static final GenericData GENERIC_DATA = GenericData.get(); + private final static Class CLS_STRING = String.class; private final static Class CLS_BIG_DECIMAL = BigDecimal.class; private final static Class CLS_BIG_INTEGER = BigInteger.class; + private final static Conversions.DecimalConversion DECIMAL_CONVERSION = new Conversions.DecimalConversion(); - private final GenericData genericData; public NonBSGenericDatumWriter(Schema root) { - super(root); - genericData = GenericData.get(); - - Map> conversions = new HashMap<>(); - if(Type.RECORD == root.getType()) { - for(Schema.Field field:root.getFields()) { - Schema fieldSchema; - - if(Type.UNION == field.schema().getType()) { - fieldSchema = AvroWriteContext.resolveUnionType(field.schema(), null); - } else { - fieldSchema = field.schema(); - } - if(null==fieldSchema.getLogicalType()) { - continue; - } - String logicalTypeName = fieldSchema.getLogicalType().getName(); - if(conversions.containsKey(logicalTypeName)){ - continue; - } - switch (logicalTypeName) { - case "decimal": - conversions.put(logicalTypeName, new Conversions.DecimalConversion()); - break; - case "date": - conversions.put(logicalTypeName, new TimeConversions.DateConversion()); - break; - case "time-millis": - conversions.put(logicalTypeName, new TimeConversions.TimeConversion()); - break; - case "time-micros": - conversions.put(logicalTypeName, new TimeConversions.TimeMicrosConversion()); - break; - case "timestamp-millis": - conversions.put(logicalTypeName, new TimeConversions.TimestampConversion()); - break; - case "timestamp-micros": - conversions.put(logicalTypeName, new TimeConversions.TimestampMicrosConversion()); - break; - default: - throw new UnsupportedOperationException( - String.format("%s is not a supported logical type.", logicalTypeName) - ); - } - } - for(Conversion conversion: conversions.values()) { - genericData.addLogicalTypeConversion(conversion); - } - } + super(root); } @Override @@ -108,7 +58,30 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio } break; case ENUM: - super.writeWithoutConversion(schema, genericData.createEnum(datum.toString(), schema), out); + super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); + return; + case FIXED: + if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { + super.writeWithoutConversion( + schema, + DECIMAL_CONVERSION.toFixed(((BigDecimal) datum), schema, schema.getLogicalType()), + out + ); + return; + } + super.writeWithoutConversion(schema, datum, out); + return; + case BYTES: + //TODO: This is ugly and I don't like the string check. + if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { + super.writeWithoutConversion( + schema, + DECIMAL_CONVERSION.toBytes(((BigDecimal) datum), schema, schema.getLogicalType()), + out + ); + return; + } + super.writeWithoutConversion(schema, datum, out); return; case INT: if (datum.getClass() == CLS_STRING) { @@ -151,7 +124,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio ((EncodedDatum) datum).write(out); return; } -// super.writeWithoutConversion(schema, datum, out); - super.write(schema, datum, out); + super.writeWithoutConversion(schema, datum, out); +// super.write(schema, datum, out); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java index b7e56650b..22549dfe6 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java @@ -5,47 +5,95 @@ import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import junit.framework.TestCase; +import org.apache.avro.Schema; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericRecord; import java.io.IOException; import java.math.BigDecimal; public class DecimalTest extends TestCase { - static class RequiredDecimal { + static class RequiredBytesDecimal { @JsonProperty(required = true) @AvroDecimal(precision = 3, scale = 3) public BigDecimal value; } - static class OptionalDecimal { + static class OptionalBytesDecimal { @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; + public BigDecimal value; + } + + static class RequiredFixedDecimal { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; + } + + static class OptionalFixedDecimal { + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; } - - public void testRequired() throws IOException { + + public void testRequiredBytesDecimal() throws IOException { final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredDecimal.class); + final AvroSchema avroSchema = mapper.schemaFor(RequiredBytesDecimal.class); System.out.println(avroSchema.getAvroSchema().toString(true)); - final RequiredDecimal expected = new RequiredDecimal(); + final RequiredBytesDecimal expected = new RequiredBytesDecimal(); expected.value = BigDecimal.valueOf(123456, 3); byte[] buffer = mapper.writer(avroSchema) .writeValueAsBytes(expected); - final RequiredDecimal actual = mapper.reader(avroSchema).forType(RequiredDecimal.class) + final RequiredBytesDecimal actual = mapper.reader(avroSchema).forType(RequiredBytesDecimal.class) .readValue(buffer); assertNotNull(actual); assertEquals(expected.value, actual.value); } - public void testOptional() throws IOException { + public void testOptionalBytesDecimal() throws IOException { final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalDecimal.class); + final AvroSchema avroSchema = mapper.schemaFor(OptionalBytesDecimal.class); System.out.println(avroSchema.getAvroSchema().toString(true)); - final OptionalDecimal expected = new OptionalDecimal(); + final OptionalBytesDecimal expected = new OptionalBytesDecimal(); + expected.value = BigDecimal.valueOf(9834780979L, 3); + final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); + expectedRecord.put("value", expected.value); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalBytesDecimal actual = mapper.reader(avroSchema).forType(OptionalBytesDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testRequiredFixedDecimal() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredFixedDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final RequiredFixedDecimal expected = new RequiredFixedDecimal(); expected.value = BigDecimal.valueOf(123456, 3); byte[] buffer = mapper.writer(avroSchema) .writeValueAsBytes(expected); - final OptionalDecimal actual = mapper.reader(avroSchema).forType(OptionalDecimal.class) + final RequiredFixedDecimal actual = mapper.reader(avroSchema).forType(RequiredFixedDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testOptionalFixedDecimal() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(OptionalFixedDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final OptionalFixedDecimal expected = new OptionalFixedDecimal(); + expected.value = BigDecimal.valueOf(9834780979L, 3); + final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); + expectedRecord.put("value", expected.value); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalFixedDecimal actual = mapper.reader(avroSchema).forType(OptionalFixedDecimal.class) .readValue(buffer); assertNotNull(actual); assertEquals(expected.value, actual.value); From c3eeb5a2be44507ed6d4e7b03252f913b72f9238 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Mon, 21 May 2018 19:16:20 -0500 Subject: [PATCH 04/25] Restructuring of the test cases. --- .../avro/logicaltypes/BytesDecimalTest.java | 50 +++++++++ .../avro/logicaltypes/DecimalTest.java | 101 ------------------ .../avro/logicaltypes/FixedDecimalTest.java | 51 +++++++++ .../logicaltypes/LogicalTypeTestCase.java | 91 ++++++++++++++++ .../avro/logicaltypes/TestData.java | 5 + .../logicaltypes/TimestampMicrosTest.java | 55 ++++++++++ .../logicaltypes/TimestampMillisTest.java | 49 +++++++++ .../logicaltypes/TimestampMillisTestOld.java | 36 +++++++ 8 files changed, 337 insertions(+), 101 deletions(-) create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java new file mode 100644 index 000000000..b48edc30f --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java @@ -0,0 +1,50 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; + +public class BytesDecimalTest extends LogicalTypeTestCase { + static final BigDecimal VALUE = BigDecimal.valueOf(123456, 3); + + @Override + protected Class dataClass() { + return BytesDecimal.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.BYTES; + } + + @Override + protected String logicalType() { + return "decimal"; + } + + @Override + protected BytesDecimal testData() { + BytesDecimal v = new BytesDecimal(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return new Conversions.DecimalConversion().toBytes(VALUE, this.schema, this.schema.getLogicalType()); + } + + static class BytesDecimal extends TestData { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + + @Override + BigDecimal value() { + return this.value; + } + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java deleted file mode 100644 index 22549dfe6..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; -import junit.framework.TestCase; -import org.apache.avro.Schema; -import org.apache.avro.generic.GenericData; -import org.apache.avro.generic.GenericRecord; - -import java.io.IOException; -import java.math.BigDecimal; - -public class DecimalTest extends TestCase { - static class RequiredBytesDecimal { - @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; - } - - static class OptionalBytesDecimal { - @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; - } - - static class RequiredFixedDecimal { - @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) - public BigDecimal value; - } - - static class OptionalFixedDecimal { - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) - public BigDecimal value; - } - - public void testRequiredBytesDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredBytesDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final RequiredBytesDecimal expected = new RequiredBytesDecimal(); - expected.value = BigDecimal.valueOf(123456, 3); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredBytesDecimal actual = mapper.reader(avroSchema).forType(RequiredBytesDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testOptionalBytesDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalBytesDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final OptionalBytesDecimal expected = new OptionalBytesDecimal(); - expected.value = BigDecimal.valueOf(9834780979L, 3); - final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); - expectedRecord.put("value", expected.value); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final OptionalBytesDecimal actual = mapper.reader(avroSchema).forType(OptionalBytesDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testRequiredFixedDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredFixedDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final RequiredFixedDecimal expected = new RequiredFixedDecimal(); - expected.value = BigDecimal.valueOf(123456, 3); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredFixedDecimal actual = mapper.reader(avroSchema).forType(RequiredFixedDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testOptionalFixedDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalFixedDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final OptionalFixedDecimal expected = new OptionalFixedDecimal(); - expected.value = BigDecimal.valueOf(9834780979L, 3); - final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); - expectedRecord.put("value", expected.value); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final OptionalFixedDecimal actual = mapper.reader(avroSchema).forType(OptionalFixedDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java new file mode 100644 index 000000000..11343f8b1 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java @@ -0,0 +1,51 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; + +public class FixedDecimalTest extends LogicalTypeTestCase { + static final BigDecimal VALUE = BigDecimal.valueOf(123456, 3); + + @Override + protected Class dataClass() { + return FixedDecimal.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.FIXED; + } + + @Override + protected String logicalType() { + return "decimal"; + } + + @Override + protected FixedDecimal testData() { + FixedDecimal v = new FixedDecimal(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return new Conversions.DecimalConversion().toFixed(VALUE, this.schema, this.schema.getLogicalType()); + } + + static class FixedDecimal extends TestData { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; + + @Override + BigDecimal value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java new file mode 100644 index 000000000..4f2bc3563 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java @@ -0,0 +1,91 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import junit.framework.TestCase; +import org.apache.avro.Schema; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.io.BinaryEncoder; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.EncoderFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public abstract class LogicalTypeTestCase extends TestCase { + protected AvroMapper mapper; + protected AvroSchema avroSchema; + protected Schema recordSchema; + protected Schema schema; + protected Class dataClass; + protected Schema.Type schemaType; + protected String logicalType; + + protected abstract Class dataClass(); + protected abstract Schema.Type schemaType(); + protected abstract String logicalType(); + protected abstract T testData(); + protected abstract Object convertedValue(); + + + @Override + protected void setUp() throws Exception { + this.mapper = new AvroMapper(); + this.dataClass = dataClass(); + + this.avroSchema = mapper.schemaFor(this.dataClass); + assertNotNull("AvroSchema should not be null", this.avroSchema); + this.recordSchema = this.avroSchema.getAvroSchema(); + assertNotNull("Schema should not be null", this.recordSchema); + assertEquals("Schema should be a record.", Schema.Type.RECORD, this.recordSchema.getType()); + Schema.Field field = this.recordSchema.getField("value"); + assertNotNull("schema must have a 'value' field", field); + this.schema = field.schema(); + this.schemaType = schemaType(); + this.logicalType = logicalType(); + + System.out.println(recordSchema.toString(true)); + } + + public void testSchemaType() { + assertEquals("schema.getType() does not match.", this.schemaType, this.schema.getType()); + } + + public void testLogicalType() { + assertNotNull("schema.getLogicalType() should not return null",this.schema.getLogicalType()); + assertEquals("schema.getLogicalType().getName() does not match.",this.logicalType, this.schema.getLogicalType().getName()); + } + + byte[] serialize(T expected) throws JsonProcessingException { + final byte[] actualbytes = this.mapper.writer(this.avroSchema).writeValueAsBytes(expected); + return actualbytes; + } + + public void testRoundTrip() throws IOException { + final T expected = testData(); + final byte[] actualbytes = serialize(expected); + final T actual = this.mapper.reader(avroSchema).forType(this.dataClass).readValue(actualbytes); + assertNotNull("actual should not be null.", actual); + assertEquals(expected.value(), actual.value()); + } + + public void testAvroSerialization() throws IOException { + final T expected = testData(); + final byte[] actualbytes = serialize(expected); + final Object convertedValue = convertedValue(); + byte[] expectedBytes; + try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(outputStream, null); + GenericData.Record record = new GenericData.Record(this.recordSchema); + record.put("value", convertedValue); + DatumWriter writer = new GenericDatumWriter(this.recordSchema); + writer.write(record, encoder); + expectedBytes = outputStream.toByteArray(); + } + + assertTrue("serialized output does not match avro version.", Arrays.equals(expectedBytes, actualbytes)); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java new file mode 100644 index 000000000..66aa7b2b1 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java @@ -0,0 +1,5 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +abstract class TestData { + abstract T value(); +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java new file mode 100644 index 000000000..ea14c161c --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java @@ -0,0 +1,55 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import junit.framework.TestCase; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; +import org.apache.avro.data.TimeConversions; + +import java.io.IOException; +import java.util.Date; + +public class TimestampMicrosTest extends LogicalTypeTestCase { + + static class RequiredTimestampMicros extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + + @Override + protected Class dataClass() { + return RequiredTimestampMicros.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + @Override + protected RequiredTimestampMicros testData() { + RequiredTimestampMicros v = new RequiredTimestampMicros(); + v.value = new Date(1526943920123L); + return v; + } + + @Override + protected Object convertedValue() { + return 1526943920123L * 1000L; + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java new file mode 100644 index 000000000..3f3965585 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java @@ -0,0 +1,49 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.Schema; + +import java.util.Date; + +public class TimestampMillisTest extends LogicalTypeTestCase { + + static class RequiredTimestampMillis extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + + @Override + protected Class dataClass() { + return RequiredTimestampMillis.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + @Override + protected RequiredTimestampMillis testData() { + RequiredTimestampMillis v = new RequiredTimestampMillis(); + v.value = new Date(1526943920123L); + return v; + } + + @Override + protected Object convertedValue() { + return 1526943920123L; + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java new file mode 100644 index 000000000..0f917fb9e --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java @@ -0,0 +1,36 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Date; + +public class TimestampMillisTestOld extends TestCase { + + static class RequiredTimestampMillis { + @JsonProperty(required = true) + @AvroTimestampMillisecond + public Date value; + } + + + public void testRoundtrip() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredTimestampMillis.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + final RequiredTimestampMillis expected = new RequiredTimestampMillis(); + expected.value = new Date(1526943920123L); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final RequiredTimestampMillis actual = mapper.reader(avroSchema).forType(RequiredTimestampMillis.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + +} From d97d4013dcce74c1861f1109caebc656dbe2fdd2 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 22 May 2018 13:30:31 -0500 Subject: [PATCH 05/25] Added more around logicalTypes and java.time. --- avro/pom.xml | 12 + .../avro/AvroAnnotationIntrospector.java | 344 ++++++++++-------- .../dataformat/avro/AvroMicroTimeModule.java | 39 ++ .../avro/apacheimpl/ApacheAvroParserImpl.java | 16 +- .../ser/TimestampMillisecondSerializers.java | 53 +++ .../dataformat/avro/SimpleGenerationTest.java | 4 +- .../avro/logicaltypes/BytesDecimalTest.java | 2 +- .../avro/logicaltypes/FixedDecimalTest.java | 2 +- .../logicaltypes/LogicalTypeTestCase.java | 6 + .../avro/logicaltypes/TestData.java | 4 +- .../logicaltypes/TimestampMicrosTest.java | 6 +- .../logicaltypes/TimestampMillisTestOld.java | 36 -- .../logicaltypes/time/TimeMillisDateTest.java | 63 ++++ .../time/TimeMillisLocalDateTimeTest.java | 65 ++++ .../time/TimeMillisOffsetDateTimeTest.java | 66 ++++ .../time/TimeMillisZonedDateTimeTest.java | 65 ++++ 16 files changed, 576 insertions(+), 207 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java diff --git a/avro/pom.xml b/avro/pom.xml index 1d9495a6d..d44c0972c 100644 --- a/avro/pom.xml +++ b/avro/pom.xml @@ -60,6 +60,10 @@ abstractions. 2.5.0 test + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + @@ -74,6 +78,14 @@ abstractions. + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index 6415a25f3..fa596af97 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -1,11 +1,5 @@ package com.fasterxml.jackson.dataformat.avro; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.avro.reflect.*; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.Version; @@ -24,6 +18,24 @@ import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.TimestampMillisecondSerializers; +import org.apache.avro.reflect.AvroAlias; +import org.apache.avro.reflect.AvroDefault; +import org.apache.avro.reflect.AvroEncode; +import org.apache.avro.reflect.AvroIgnore; +import org.apache.avro.reflect.AvroName; +import org.apache.avro.reflect.CustomEncoding; +import org.apache.avro.reflect.Nullable; +import org.apache.avro.reflect.Stringable; +import org.apache.avro.reflect.Union; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; /** * Adds support for the following annotations from the Apache Avro implementation: @@ -31,8 +43,8 @@ *

  • {@link AvroIgnore @AvroIgnore} - Alias for JsonIgnore
  • *
  • {@link AvroName @AvroName("custom Name")} - Alias for JsonProperty("custom name")
  • *
  • {@link AvroDefault @AvroDefault("default value")} - Alias for JsonProperty.defaultValue, to - * define default value for generated Schemas - *
  • + * define default value for generated Schemas + * *
  • {@link Nullable @Nullable} - Alias for JsonProperty(required = false)
  • *
  • {@link Stringable @Stringable} - Alias for JsonCreator on the constructor and JsonValue on * the {@link #toString()} method.
  • @@ -41,156 +53,170 @@ * * @since 2.9 */ -public class AvroAnnotationIntrospector extends AnnotationIntrospector -{ - private static final long serialVersionUID = 1L; - - public AvroAnnotationIntrospector() { } - - @Override - public Version version() { - return PackageVersion.VERSION; - } - - @Override - public boolean hasIgnoreMarker(AnnotatedMember m) { - return _findAnnotation(m, AvroIgnore.class) != null; - } - - @Override - public PropertyName findNameForSerialization(Annotated a) { - return _findName(a); - } - - @Override - public PropertyName findNameForDeserialization(Annotated a) { - return _findName(a); - } - - @Override - public Object findDeserializer(Annotated am) { - AvroEncode ann = _findAnnotation(am, AvroEncode.class); - if (ann != null) { - return new CustomEncodingDeserializer<>((CustomEncoding)ClassUtil.createInstance(ann.using(), true)); - } - return null; - } - - @Override - public String findPropertyDefaultValue(Annotated m) { - AvroDefault ann = _findAnnotation(m, AvroDefault.class); - return (ann == null) ? null : ann.value(); - } - - @Override - public List findPropertyAliases(Annotated m) { - AvroAlias ann = _findAnnotation(m, AvroAlias.class); - if (ann == null) { - return null; - } - return Collections.singletonList(PropertyName.construct(ann.alias())); - } - - protected PropertyName _findName(Annotated a) - { - AvroName ann = _findAnnotation(a, AvroName.class); - return (ann == null) ? null : PropertyName.construct(ann.value()); - } - - @Override - public Boolean hasRequiredMarker(AnnotatedMember m) { - if (_hasAnnotation(m, Nullable.class)) { - return Boolean.FALSE; - } - return null; - } - - @Override - public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { - if (a instanceof AnnotatedConstructor) { - AnnotatedConstructor constructor = (AnnotatedConstructor) a; - // 09-Mar-2017, tatu: Ideally would allow mix-ins etc, but for now let's take - // a short-cut here: - Class declClass = constructor.getDeclaringClass(); - if (declClass.getAnnotation(Stringable.class) != null) { - if (constructor.getParameterCount() == 1 - && String.class.equals(constructor.getRawParameterType(0))) { - return JsonCreator.Mode.DELEGATING; - } - } - } - return null; - } - - @Override - public Object findSerializer(Annotated a) { - if (a.hasAnnotation(Stringable.class)) { - return ToStringSerializer.class; - } - AvroEncode ann = _findAnnotation(a, AvroEncode.class); - if (ann != null) { - return new CustomEncodingSerializer<>((CustomEncoding)ClassUtil.createInstance(ann.using(), true)); - } - return null; - } - - @Override - public List findSubtypes(Annotated a) - { - Class[] types = _getUnionTypes(a); - if (types == null) { - return null; - } - ArrayList names = new ArrayList<>(types.length); - for (Class subtype : types) { - names.add(new NamedType(subtype, AvroSchemaHelper.getTypeId(subtype))); +public class AvroAnnotationIntrospector extends AnnotationIntrospector { + private static final long serialVersionUID = 1L; + + public AvroAnnotationIntrospector() { + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + @Override + public boolean hasIgnoreMarker(AnnotatedMember m) { + return _findAnnotation(m, AvroIgnore.class) != null; + } + + @Override + public PropertyName findNameForSerialization(Annotated a) { + return _findName(a); + } + + @Override + public PropertyName findNameForDeserialization(Annotated a) { + return _findName(a); + } + + @Override + public Object findDeserializer(Annotated am) { + AvroEncode ann = _findAnnotation(am, AvroEncode.class); + if (ann != null) { + return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); + } + return null; + } + + @Override + public String findPropertyDefaultValue(Annotated m) { + AvroDefault ann = _findAnnotation(m, AvroDefault.class); + return (ann == null) ? null : ann.value(); + } + + @Override + public List findPropertyAliases(Annotated m) { + AvroAlias ann = _findAnnotation(m, AvroAlias.class); + if (ann == null) { + return null; + } + return Collections.singletonList(PropertyName.construct(ann.alias())); + } + + protected PropertyName _findName(Annotated a) { + AvroName ann = _findAnnotation(a, AvroName.class); + return (ann == null) ? null : PropertyName.construct(ann.value()); + } + + @Override + public Boolean hasRequiredMarker(AnnotatedMember m) { + if (_hasAnnotation(m, Nullable.class)) { + return Boolean.FALSE; + } + return null; + } + + @Override + public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { + if (a instanceof AnnotatedConstructor) { + AnnotatedConstructor constructor = (AnnotatedConstructor) a; + // 09-Mar-2017, tatu: Ideally would allow mix-ins etc, but for now let's take + // a short-cut here: + Class declClass = constructor.getDeclaringClass(); + if (declClass.getAnnotation(Stringable.class) != null) { + if (constructor.getParameterCount() == 1 + && String.class.equals(constructor.getRawParameterType(0))) { + return JsonCreator.Mode.DELEGATING; } - return names; - } - - @Override - public TypeResolverBuilder findTypeResolver(MapperConfig config, AnnotatedClass ac, JavaType baseType) { - return _findTypeResolver(config, ac, baseType); - } - - @Override - public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, AnnotatedMember am, JavaType baseType) { - return _findTypeResolver(config, am, baseType); - } - - @Override - public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig config, AnnotatedMember am, JavaType containerType) { - return _findTypeResolver(config, am, containerType); - } - - protected TypeResolverBuilder _findTypeResolver(MapperConfig config, Annotated ann, JavaType baseType) { - // 14-Apr-2017, tatu: There are two ways to enable polymorphic typing, above and beyond - // basic Jackson: use of `@Union`, and "default typing" approach for `java.lang.Object`: - // latter since Avro support for "untyped" values is otherwise difficult. - // This seems to work for now, but maybe needs more work in future... - if (baseType.isJavaLangObject() || (_getUnionTypes(ann) != null)) { - TypeResolverBuilder resolver = new AvroTypeResolverBuilder(); - JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) { - resolver = resolver.defaultImpl(typeInfo.defaultImpl()); - } - return resolver; - } - return null; - } - - protected Class[] _getUnionTypes(Annotated a) { - Union ann = _findAnnotation(a, Union.class); - if (ann != null) { - // 14-Apr-2017, tatu: I think it makes sense to require non-empty List, as this allows - // disabling annotation with overrides. But one could even consider requiring more than - // one (where single type is not really polymorphism)... for now, however, just one - // is acceptable, and maybe that has valid usages. - Class[] c = ann.value(); - if (c.length > 0) { - return c; - } - } - return null; - } + } + } + return null; + } + + @Override + public Object findSerializer(Annotated a) { + if (a.hasAnnotation(Stringable.class)) { + return ToStringSerializer.class; + } + AvroEncode ann = _findAnnotation(a, AvroEncode.class); + if (ann != null) { + return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (timestampMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return TimestampMillisecondSerializers.DATE; + } + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return TimestampMillisecondSerializers.LOCAL_DATE_TIME; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return TimestampMillisecondSerializers.ZONED_DATE_TIME; + } + if(a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return TimestampMillisecondSerializers.OFFSET_DATE_TIME; + } + } + + return null; + } + + @Override + public List findSubtypes(Annotated a) { + Class[] types = _getUnionTypes(a); + if (types == null) { + return null; + } + ArrayList names = new ArrayList<>(types.length); + for (Class subtype : types) { + names.add(new NamedType(subtype, AvroSchemaHelper.getTypeId(subtype))); + } + return names; + } + + @Override + public TypeResolverBuilder findTypeResolver(MapperConfig config, AnnotatedClass ac, JavaType baseType) { + return _findTypeResolver(config, ac, baseType); + } + + @Override + public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, AnnotatedMember am, JavaType baseType) { + return _findTypeResolver(config, am, baseType); + } + + @Override + public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig config, AnnotatedMember am, JavaType containerType) { + return _findTypeResolver(config, am, containerType); + } + + protected TypeResolverBuilder _findTypeResolver(MapperConfig config, Annotated ann, JavaType baseType) { + // 14-Apr-2017, tatu: There are two ways to enable polymorphic typing, above and beyond + // basic Jackson: use of `@Union`, and "default typing" approach for `java.lang.Object`: + // latter since Avro support for "untyped" values is otherwise difficult. + // This seems to work for now, but maybe needs more work in future... + if (baseType.isJavaLangObject() || (_getUnionTypes(ann) != null)) { + TypeResolverBuilder resolver = new AvroTypeResolverBuilder(); + JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) { + resolver = resolver.defaultImpl(typeInfo.defaultImpl()); + } + return resolver; + } + return null; + } + + protected Class[] _getUnionTypes(Annotated a) { + Union ann = _findAnnotation(a, Union.class); + if (ann != null) { + // 14-Apr-2017, tatu: I think it makes sense to require non-empty List, as this allows + // disabling annotation with overrides. But one could even consider requiring more than + // one (where single type is not really polymorphism)... for now, however, just one + // is acceptable, and maybe that has valid usages. + Class[] c = ann.value(); + if (c.length > 0) { + return c; + } + } + return null; + } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java new file mode 100644 index 000000000..ea41cad9c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java @@ -0,0 +1,39 @@ +package com.fasterxml.jackson.dataformat.avro; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +public class AvroMicroTimeModule extends SimpleModule { + public AvroMicroTimeModule() { + super.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); + + } + + + static class LocalDateTimeSerializer extends StdSerializer { + protected LocalDateTimeSerializer() { + super(LocalDateTime.class); + } + + + + @Override + public void serializeWithType(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { + super.serializeWithType(value, gen, serializers, typeSer); + } + + @Override + public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + localDateTime.toInstant(ZoneOffset.UTC).getNano() * 1000L + ); + } + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java index 0f187e97a..4214d96d0 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; import org.apache.avro.io.BinaryDecoder; @@ -374,22 +376,28 @@ public int decodeEnum() throws IOException { @Override public JsonToken decodeBytesDecimal(int scale) throws IOException { - return null; + decodeBytes(); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; } @Override public void skipBytesDecimal() throws IOException { - + skipBytes(); } @Override public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { - return null; + decodeFixed(size); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; } @Override public void skipFixedDecimal(int size) throws IOException { - + skipFixed(size); } /* diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java new file mode 100644 index 000000000..0747e19ca --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; + +public class TimestampMillisecondSerializers { + public static final JsonSerializer LOCAL_DATE_TIME = new LocalDateTimeSerializer(); + public static final JsonSerializer DATE = new DateSerializer(); + public static final JsonSerializer ZONED_DATE_TIME = new ZonedDateTimeSerializer(); + public static final JsonSerializer OFFSET_DATE_TIME = new OffsetDateTimeSerializer(); + + static class DateSerializer extends JsonSerializer { + @Override + public void serialize(Date d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(d.getTime()); + } + } + + static class LocalDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(LocalDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant(ZoneOffset.UTC).toEpochMilli() + ); + } + } + + static class ZonedDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(ZonedDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant().toEpochMilli() + ); + } + } + + static class OffsetDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(OffsetDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant().toEpochMilli() + ); + } + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java index 3736b8d1b..c7283592e 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java @@ -98,13 +98,13 @@ public void testSimplest() throws Exception public void testBinaryOk() throws Exception { ObjectMapper mapper = new ObjectMapper(new AvroFactory()); - Binary bin = new Binary("Foo", new byte[] { 1, 2, 3, 4 }); + Binary bin = new Binary("LocalDateTimeSerializer", new byte[] { 1, 2, 3, 4 }); byte[] bytes = mapper.writer(SCHEMA_WITH_BINARY_JSON).writeValueAsBytes(bin); assertEquals(9, bytes.length); assertNotNull(bytes); Binary output = mapper.reader(SCHEMA_WITH_BINARY_JSON).forType(Binary.class).readValue(bytes); assertNotNull(output); - assertEquals("Foo", output.name); + assertEquals("LocalDateTimeSerializer", output.name); assertNotNull(output.value); Assert.assertArrayEquals(bin.value, output.value); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java index b48edc30f..6212b0131 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java @@ -43,7 +43,7 @@ static class BytesDecimal extends TestData { public BigDecimal value; @Override - BigDecimal value() { + public BigDecimal value() { return this.value; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java index 11343f8b1..a6582bcf2 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java @@ -43,7 +43,7 @@ static class FixedDecimal extends TestData { public BigDecimal value; @Override - BigDecimal value() { + public BigDecimal value() { return this.value; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java index 4f2bc3563..724abd140 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import junit.framework.TestCase; import org.apache.avro.Schema; @@ -48,6 +49,11 @@ protected void setUp() throws Exception { this.logicalType = logicalType(); System.out.println(recordSchema.toString(true)); + configure(this.mapper); + } + + protected void configure(AvroMapper mapper) { + } public void testSchemaType() { diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java index 66aa7b2b1..5ba0e240f 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java @@ -1,5 +1,5 @@ package com.fasterxml.jackson.dataformat.avro.logicaltypes; -abstract class TestData { - abstract T value(); +public abstract class TestData { + public abstract T value(); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java index ea14c161c..efee3e6f4 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java @@ -26,6 +26,8 @@ public Date value() { } } + static final Date VALUE = new Date(1526943920123L); + @Override protected Class dataClass() { return RequiredTimestampMicros.class; @@ -44,12 +46,12 @@ protected String logicalType() { @Override protected RequiredTimestampMicros testData() { RequiredTimestampMicros v = new RequiredTimestampMicros(); - v.value = new Date(1526943920123L); + v.value = VALUE; return v; } @Override protected Object convertedValue() { - return 1526943920123L * 1000L; + return VALUE.getTime() * 1000L; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java deleted file mode 100644 index 0f917fb9e..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import junit.framework.TestCase; - -import java.io.IOException; -import java.util.Date; - -public class TimestampMillisTestOld extends TestCase { - - static class RequiredTimestampMillis { - @JsonProperty(required = true) - @AvroTimestampMillisecond - public Date value; - } - - - public void testRoundtrip() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredTimestampMillis.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - final RequiredTimestampMillis expected = new RequiredTimestampMillis(); - expected.value = new Date(1526943920123L); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredTimestampMillis actual = mapper.reader(avroSchema).forType(RequiredTimestampMillis.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java new file mode 100644 index 000000000..498f98a18 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +public class TimeMillisDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final Date VALUE = new Date(1526955327123L); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java new file mode 100644 index 000000000..23660f2ae --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java @@ -0,0 +1,65 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimeMillisLocalDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java new file mode 100644 index 000000000..70fe24382 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java @@ -0,0 +1,66 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TimeMillisOffsetDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + ZonedDateTime value; + + @Override + public ZonedDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java new file mode 100644 index 000000000..460f0a3d4 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java @@ -0,0 +1,65 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimeMillisZonedDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} From 386a4f0c7e6e96e401fa7e762b86be4186c78b00 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 13:18:01 -0500 Subject: [PATCH 06/25] Refactored java8 classes out to it's own module. This will allow the avro module to stay java 7 while supporting mapping of java.time out to their own serializers. --- avro-java8/pom.xml | 96 ++++++++++++ .../AvroJavaTimeAnnotationIntrospector.java | 140 ++++++++++++++++++ .../avro/java8/AvroJavaTimeModule.java | 20 +++ .../avro/java8/PackageVersion.java.in | 20 +++ .../java8/deser/BaseTimeJsonDeserializer.java | 29 ++++ .../java8/deser/LocalDateDeserializer.java | 18 +++ .../deser/LocalDateTimeDeserializer.java | 21 +++ .../java8/deser/LocalTimeDeserializer.java | 28 ++++ .../deser/OffsetDateTimeDeserializer.java | 21 +++ .../deser/ZonedDateTimeDeserializer.java | 21 +++ .../java8/ser/BaseTimeJsonSerializer.java | 41 +++++ .../avro/java8/ser/LocalDateSerializer.java | 17 +++ .../java8/ser/LocalDateTimeSerializer.java | 22 +++ .../avro/java8/ser/LocalTimeSerializer.java | 38 +++++ .../java8/ser/OffsetDateTimeSerializer.java | 21 +++ .../java8/ser/ZonedDateTimeSerializer.java | 21 +++ .../java8}/logicaltypes/BytesDecimalTest.java | 2 +- .../java8}/logicaltypes/FixedDecimalTest.java | 2 +- .../logicaltypes/LogicalTypeTestCase.java | 16 +- .../avro/java8}/logicaltypes/TestData.java | 2 +- .../logicaltypes/TimestampMicrosTest.java | 9 +- .../avro/java8/logicaltypes/UUIDTest.java | 52 +++++++ .../logicaltypes/time/DateLocalDateTest.java | 60 ++++++++ .../time/TimeMicrosLocalTimeTest.java | 60 ++++++++ .../time/TimeMillisLocalTimeTest.java | 59 ++++++++ .../time/TimestampMicrosDateTest.java | 52 +++++++ .../TimestampMicrosLocalDateTimeTest.java | 67 +++++++++ .../TimestampMicrosOffsetDateTimeTest.java | 58 ++++++++ .../TimestampMicrosZonedDateTimeTest.java | 20 +-- .../time/TimestampMillisDateTest.java | 13 +- .../TimestampMillisLocalDateTimeTest.java | 16 +- .../TimestampMillisOffsetDateTimeTest.java | 21 ++- .../TimestampMillisZonedDateTimeTest.java | 57 +++++++ .../avro/AvroAnnotationIntrospector.java | 54 +++++-- .../dataformat/avro/AvroMicroTimeModule.java | 39 ----- .../jackson/dataformat/avro/AvroUUID.java | 19 +++ .../AvroDateTimestampMicrosDeserializer.java | 24 +++ .../AvroDateTimestampMillisDeserializer.java | 19 +++ .../avro/deser/AvroReaderFactory.java | 34 ++--- .../avro/deser/AvroUUIDDeserializer.java | 18 +++ .../dataformat/avro/schema/RecordVisitor.java | 44 +++--- .../AvroDateTimestampMicrosSerializer.java | 17 +++ .../AvroDateTimestampMillisSerializer.java | 17 +++ .../avro/ser/AvroUUIDSerializer.java | 17 +++ .../ser/TimestampMillisecondSerializers.java | 53 ------- .../dataformat/avro/SimpleGenerationTest.java | 4 +- .../logicaltypes/TimestampMillisTest.java | 49 ------ .../avro/schema/TestLogicalTypes.java | 16 ++ pom.xml | 1 + 49 files changed, 1313 insertions(+), 252 deletions(-) create mode 100644 avro-java8/pom.xml create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/BytesDecimalTest.java (94%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/FixedDecimalTest.java (95%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/LogicalTypeTestCase.java (90%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/TestData.java (50%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/TimestampMicrosTest.java (74%) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java (63%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java (66%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java (65%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java (59%) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml new file mode 100644 index 000000000..ee763eeb2 --- /dev/null +++ b/avro-java8/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + com.fasterxml.jackson.dataformat + jackson-dataformats-binary + 2.9.6-SNAPSHOT + + jackson-dataformat-avro-java8 + Jackson dataformat: Avro Java 8 + bundle + Support for reading and writing AVRO-encoded data via Jackson +abstractions. + + http://github.com/FasterXML/jackson-dataformats-binary + + + + com/fasterxml/jackson/dataformat/avro/java8 + ${project.groupId}.avro.java8 + + + + + + com.fasterxml.jackson.core + jackson-annotations + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-avro + ${project.version} + + + org.apache.avro + avro + 1.8.1 + + + + + ch.qos.logback + logback-classic + 1.1.3 + test + + + + org.projectlombok + lombok + 1.16.14 + test + + + + + org.assertj + assertj-core + 2.5.0 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + + + com.google.code.maven-replacer-plugin + replacer + + + process-packageVersion + generate-sources + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java new file mode 100644 index 000000000..745b67a70 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -0,0 +1,140 @@ +package com.fasterxml.jackson.dataformat.avro.java8; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.AnnotationIntrospector; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.OffsetDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.ZonedDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.OffsetDateTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.ZonedDateTimeSerializer; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +class AvroJavaTimeAnnotationIntrospector extends AnnotationIntrospector { + static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector(); + + @Override + public Object findSerializer(Annotated a) { + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (null != timestampMillisecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MILLIS; + } + } + + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (null != timestampMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MICROS; + } + } + + AvroDate date = _findAnnotation(a, AvroDate.class); + if (null != date) { + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateSerializer.INSTANCE; + } + } + + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (null != timeMillisecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MILLIS; + } + } + + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (null != timeMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MICROS; + } + } + + return super.findSerializer(a); + + } + + @Override + public Object findDeserializer(Annotated a) { + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (null != timestampMillisecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MILLIS; + } + } + + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (null != timestampMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MICROS; + } + } + + AvroDate date = _findAnnotation(a, AvroDate.class); + if (null != date) { + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateDeserializer.INSTANCE; + } + } + + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (null != timeMillisecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MILLIS; + } + } + + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (null != timeMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MICROS; + } + } + + return super.findDeserializer(a); + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java new file mode 100644 index 000000000..977a21286 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java @@ -0,0 +1,20 @@ +package com.fasterxml.jackson.dataformat.avro.java8; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.dataformat.avro.AvroModule; + +public class AvroJavaTimeModule extends AvroModule { + + + public AvroJavaTimeModule() { + withAnnotationIntrospector(AvroJavaTimeAnnotationIntrospector.INSTANCE); + } + + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in new file mode 100644 index 000000000..7860aa14b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in @@ -0,0 +1,20 @@ +package @package@; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.Versioned; +import com.fasterxml.jackson.core.util.VersionUtil; + +/** + * Automatically generated from PackageVersion.java.in during + * packageVersion-generate execution of maven-replacer-plugin in + * pom.xml. + */ +public final class PackageVersion implements Versioned { + public final static Version VERSION = VersionUtil.parseVersion( + "@projectversion@", "@projectgroupid@", "@projectartifactid@"); + + @Override + public Version version() { + return VERSION; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java new file mode 100644 index 000000000..2e690b36c --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { + final TimeUnit resolution; + final ZoneId zoneId = ZoneId.of("UTC"); + + BaseTimeJsonDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + abstract T fromInstant(Instant input); + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final long input = p.getLongValue(); + final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + final Instant instant = Instant.ofEpochMilli(output); + return fromInstant(instant); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java new file mode 100644 index 000000000..41b321b25 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalDate; + +public class LocalDateDeserializer extends JsonDeserializer { + public static final JsonDeserializer INSTANCE = new LocalDateDeserializer(); + + @Override + public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + return LocalDate.ofEpochDay(jsonParser.getLongValue()); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java new file mode 100644 index 000000000..b63790dc9 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +public class LocalDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new LocalDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new LocalDateTimeDeserializer(TimeUnit.MICROSECONDS); + + LocalDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected LocalDateTime fromInstant(Instant input) { + return LocalDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java new file mode 100644 index 000000000..0d2a24f2b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java @@ -0,0 +1,28 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class LocalTimeDeserializer extends JsonDeserializer { + public static JsonDeserializer MILLIS = new LocalTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new LocalTimeDeserializer(TimeUnit.MICROSECONDS); + + final TimeUnit resolution; + + LocalTimeDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public LocalTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + long nanos = this.resolution.toNanos(value); + return LocalTime.ofNanoOfDay(nanos); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java new file mode 100644 index 000000000..80b472b0c --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.concurrent.TimeUnit; + +public class OffsetDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new OffsetDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new OffsetDateTimeDeserializer(TimeUnit.MICROSECONDS); + + OffsetDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected OffsetDateTime fromInstant(Instant input) { + return OffsetDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java new file mode 100644 index 000000000..c1ae6eae6 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +public class ZonedDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new ZonedDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new ZonedDateTimeDeserializer(TimeUnit.MICROSECONDS); + + ZonedDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected ZonedDateTime fromInstant(Instant input) { + return ZonedDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java new file mode 100644 index 000000000..04ab716ba --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java @@ -0,0 +1,41 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +public abstract class BaseTimeJsonSerializer extends JsonSerializer { + final TimeUnit resolution; + final ZoneId zoneId = ZoneId.of("UTC"); + + BaseTimeJsonSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + abstract Instant toInstant(T input); + + @Override + public void serialize(T input, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final Instant instant = toInstant(input); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + + TimeUnit.NANOSECONDS.toMicros(instant.getNano()); + break; + case MILLISECONDS: + output = instant.toEpochMilli(); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + jsonGenerator.writeNumber(output); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java new file mode 100644 index 000000000..7a37aedfd --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDate; + +public class LocalDateSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new LocalDateSerializer(); + + @Override + public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(localDate.toEpochDay()); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java new file mode 100644 index 000000000..f40c3d6df --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java @@ -0,0 +1,22 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; + +public class LocalDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new LocalDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new LocalDateTimeSerializer(TimeUnit.MICROSECONDS); + + LocalDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(LocalDateTime input) { + return input.toInstant(ZoneOffset.UTC); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java new file mode 100644 index 000000000..eb6268765 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java @@ -0,0 +1,38 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class LocalTimeSerializer extends JsonSerializer { + public static final JsonSerializer MILLIS = new LocalTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new LocalTimeSerializer(TimeUnit.MICROSECONDS); + + private final TimeUnit resolution; + + LocalTimeSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public void serialize(LocalTime localTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + switch (this.resolution) { + case MICROSECONDS: + long micros = TimeUnit.NANOSECONDS.toMicros(localTime.toNanoOfDay()); + jsonGenerator.writeNumber(micros); + break; + case MILLISECONDS: + int millis = (int)TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay()); + jsonGenerator.writeNumber(millis); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java new file mode 100644 index 000000000..65f14147b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.concurrent.TimeUnit; + +public class OffsetDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new OffsetDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new OffsetDateTimeSerializer(TimeUnit.MICROSECONDS); + + OffsetDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(OffsetDateTime input) { + return input.toInstant(); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java new file mode 100644 index 000000000..7cc61b697 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +public class ZonedDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new ZonedDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new ZonedDateTimeSerializer(TimeUnit.MICROSECONDS); + + ZonedDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(ZonedDateTime input) { + return input.toInstant(); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java similarity index 94% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java index 6212b0131..6b3fffce0 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDecimal; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java similarity index 95% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java index a6582bcf2..410b9b606 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDecimal; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java similarity index 90% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java index 724abd140..e6475a6bf 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java @@ -1,9 +1,9 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import junit.framework.TestCase; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; @@ -26,15 +26,19 @@ public abstract class LogicalTypeTestCase extends TestCase { protected String logicalType; protected abstract Class dataClass(); + protected abstract Schema.Type schemaType(); + protected abstract String logicalType(); + protected abstract T testData(); + protected abstract Object convertedValue(); @Override protected void setUp() throws Exception { - this.mapper = new AvroMapper(); + this.mapper = new AvroMapper(new AvroJavaTimeModule()); this.dataClass = dataClass(); this.avroSchema = mapper.schemaFor(this.dataClass); @@ -61,8 +65,8 @@ public void testSchemaType() { } public void testLogicalType() { - assertNotNull("schema.getLogicalType() should not return null",this.schema.getLogicalType()); - assertEquals("schema.getLogicalType().getName() does not match.",this.logicalType, this.schema.getLogicalType().getName()); + assertNotNull("schema.getLogicalType() should not return null", this.schema.getLogicalType()); + assertEquals("schema.getLogicalType().getName() does not match.", this.logicalType, this.schema.getLogicalType().getName()); } byte[] serialize(T expected) throws JsonProcessingException { @@ -83,7 +87,7 @@ public void testAvroSerialization() throws IOException { final byte[] actualbytes = serialize(expected); final Object convertedValue = convertedValue(); byte[] expectedBytes; - try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(outputStream, null); GenericData.Record record = new GenericData.Record(this.recordSchema); record.put("value", convertedValue); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java similarity index 50% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java index 5ba0e240f..c01309015 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; public abstract class TestData { public abstract T value(); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java similarity index 74% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java index efee3e6f4..8fc368658 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java @@ -1,16 +1,9 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import junit.framework.TestCase; -import org.apache.avro.Conversions; import org.apache.avro.Schema; -import org.apache.avro.data.TimeConversions; -import java.io.IOException; import java.util.Date; public class TimestampMicrosTest extends LogicalTypeTestCase { diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java new file mode 100644 index 000000000..69c875d8f --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; +import java.util.UUID; + +public class UUIDTest extends LogicalTypeTestCase { + static final UUID VALUE = UUID.randomUUID(); + + @Override + protected Class dataClass() { + return UUIDTestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.STRING; + } + + @Override + protected String logicalType() { + return "uuid"; + } + + @Override + protected UUIDTestCase testData() { + UUIDTestCase v = new UUIDTestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toString(); + } + + static class UUIDTestCase extends TestData { + @JsonProperty(required = true) + @AvroUUID + public UUID value; + + @Override + public UUID value() { + return this.value; + } + } +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java new file mode 100644 index 000000000..fe5673aa1 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.util.Date; + +public class DateLocalDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "date"; + } + + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toEpochDay(); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroDate + LocalDate value; + + @Override + public LocalDate value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java new file mode 100644 index 000000000..8c010ef1e --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class TimeMicrosLocalTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "time-micros"; + } + + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return TimeUnit.NANOSECONDS.toMicros(VALUE.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMicrosecond + LocalTime value; + + @Override + public LocalTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java new file mode 100644 index 000000000..77ca03b6e --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -0,0 +1,59 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class TimeMillisLocalTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return TimeUnit.NANOSECONDS.toMillis(VALUE.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + LocalTime value; + + @Override + public LocalTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java new file mode 100644 index 000000000..79a0cb266 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.util.Date; + +public class TimestampMicrosDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final Date VALUE = new Date(1526955327123L); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L * 1000L; + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java new file mode 100644 index 000000000..0fc78f652 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -0,0 +1,67 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; + +public class TimestampMicrosLocalDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + Instant instant = VALUE.toInstant(ZoneOffset.UTC); + return (TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + TimeUnit.NANOSECONDS.toMicros(instant.getNano())); + } + + @Override + protected void configure(AvroMapper mapper) { + mapper.registerModule(new AvroJavaTimeModule()); + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java new file mode 100644 index 000000000..bafaea035 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L * 1000L; + } + + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + OffsetDateTime value; + + @Override + public OffsetDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java similarity index 63% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index 70fe24382..c39af3fb8 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -1,12 +1,12 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; @@ -14,7 +14,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; -public class TimeMillisOffsetDateTimeTest extends LogicalTypeTestCase { +public class TimestampMicrosZonedDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -27,7 +27,7 @@ protected Schema.Type schemaType() { @Override protected String logicalType() { - return "timestamp-millis"; + return "timestamp-micros"; } static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( @@ -44,17 +44,17 @@ protected TestCase testData() { @Override protected Object convertedValue() { - return 1526955327123L; + return 1526955327123L * 1000L; } @Override protected void configure(AvroMapper mapper) { - + mapper.registerModule(new AvroJavaTimeModule()); } static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroTimestampMicrosecond ZonedDateTime value; @Override diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java similarity index 66% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index 498f98a18..65f9d6444 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -1,20 +1,15 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.util.Date; -public class TimeMillisDateTest extends LogicalTypeTestCase { +public class TimestampMillisDateTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java similarity index 65% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index 23660f2ae..8e7526d91 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -1,19 +1,16 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -public class TimeMillisLocalDateTimeTest extends LogicalTypeTestCase { +public class TimestampMillisLocalDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -46,11 +43,6 @@ protected Object convertedValue() { return 1526955327123L; } - @Override - protected void configure(AvroMapper mapper) { - - } - static class TestCase extends TestData { @JsonProperty(required = true) @AvroTimestampMillisecond diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java similarity index 59% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index 460f0a3d4..b68002156 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -1,19 +1,18 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; -public class TimeMillisZonedDateTimeTest extends LogicalTypeTestCase { +public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -29,7 +28,7 @@ protected String logicalType() { return "timestamp-millis"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( Instant.ofEpochMilli(1526955327123L), ZoneId.of("UTC") ); @@ -51,13 +50,13 @@ protected void configure(AvroMapper mapper) { } - static class TestCase extends TestData { + static class TestCase extends TestData { @JsonProperty(required = true) @AvroTimestampMillisecond - LocalDateTime value; + OffsetDateTime value; @Override - public LocalDateTime value() { + public OffsetDateTime value() { return this.value; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java new file mode 100644 index 000000000..aa7e13e66 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -0,0 +1,57 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TimestampMillisZonedDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + ZonedDateTime value; + + @Override + public ZonedDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index fa596af97..e6e5aed10 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,9 +16,14 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMicrosDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMillisDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMicrosSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMillisSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; -import com.fasterxml.jackson.dataformat.avro.ser.TimestampMillisecondSerializers; import org.apache.avro.reflect.AvroAlias; import org.apache.avro.reflect.AvroDefault; import org.apache.avro.reflect.AvroEncode; @@ -29,13 +34,11 @@ import org.apache.avro.reflect.Stringable; import org.apache.avro.reflect.Union; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.UUID; /** * Adds support for the following annotations from the Apache Avro implementation: @@ -80,11 +83,31 @@ public PropertyName findNameForDeserialization(Annotated a) { } @Override - public Object findDeserializer(Annotated am) { - AvroEncode ann = _findAnnotation(am, AvroEncode.class); + public Object findDeserializer(Annotated a) { + AvroEncode ann = _findAnnotation(a, AvroEncode.class); if (ann != null) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (timestampMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMillisDeserializer.INSTANCE; + } + } + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (timestampMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMicrosDeserializer.INSTANCE; + } + } + AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); + if (avroUUID != null) { + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDDeserializer.INSTANCE; + } + } + return null; } @@ -145,16 +168,19 @@ public Object findSerializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return TimestampMillisecondSerializers.DATE; + return AvroDateTimestampMillisSerializer.INSTANCE; } - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return TimestampMillisecondSerializers.LOCAL_DATE_TIME; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return TimestampMillisecondSerializers.ZONED_DATE_TIME; + } + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (timestampMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMicrosSerializer.INSTANCE; } - if(a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return TimestampMillisecondSerializers.OFFSET_DATE_TIME; + } + AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); + if (avroUUID != null) { + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDSerializer.INSTANCE; } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java deleted file mode 100644 index ea41cad9c..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -public class AvroMicroTimeModule extends SimpleModule { - public AvroMicroTimeModule() { - super.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); - - } - - - static class LocalDateTimeSerializer extends StdSerializer { - protected LocalDateTimeSerializer() { - super(LocalDateTime.class); - } - - - - @Override - public void serializeWithType(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { - super.serializeWithType(value, gen, serializers, typeSer); - } - - @Override - public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - localDateTime.toInstant(ZoneOffset.UTC).getNano() * 1000L - ); - } - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java new file mode 100644 index 000000000..27acd79f5 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java @@ -0,0 +1,19 @@ +package com.fasterxml.jackson.dataformat.avro; + +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroUUID { +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java new file mode 100644 index 000000000..bde804621 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java @@ -0,0 +1,24 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMicrosDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroDateTimestampMicrosDeserializer(); + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + + if (value == 0L) { + return new Date(value); + } + + return new Date(value / 1000L); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java new file mode 100644 index 000000000..53039802a --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java @@ -0,0 +1,19 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMillisDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroDateTimestampMillisDeserializer(); + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + return new Date(value); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java index 50b21176c..8a9d7d30c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java @@ -2,27 +2,25 @@ import java.util.*; +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.util.internal.JacksonUtils; -import com.fasterxml.jackson.dataformat.avro.deser.ScalarDecoder.*; -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; - /** * Helper class used for constructing a hierarchic reader for given * (reader-) schema. */ public abstract class AvroReaderFactory { - protected final static ScalarDecoder READER_BOOLEAN = new BooleanDecoder(); - protected final static ScalarDecoder READER_BYTES = new BytesDecoder(); - protected final static ScalarDecoder READER_DOUBLE = new DoubleReader(); - protected final static ScalarDecoder READER_FLOAT = new FloatReader(); - protected final static ScalarDecoder READER_INT = new IntReader(); - protected final static ScalarDecoder READER_LONG = new LongReader(); - protected final static ScalarDecoder READER_NULL = new NullReader(); - protected final static ScalarDecoder READER_STRING = new StringReader(); + protected final static ScalarDecoder READER_BOOLEAN = new ScalarDecoder.BooleanDecoder(); + protected final static ScalarDecoder READER_BYTES = new ScalarDecoder.BytesDecoder(); + protected final static ScalarDecoder READER_DOUBLE = new ScalarDecoder.DoubleReader(); + protected final static ScalarDecoder READER_FLOAT = new ScalarDecoder.FloatReader(); + protected final static ScalarDecoder READER_INT = new ScalarDecoder.IntReader(); + protected final static ScalarDecoder READER_LONG = new ScalarDecoder.LongReader(); + protected final static ScalarDecoder READER_NULL = new ScalarDecoder.NullReader(); + protected final static ScalarDecoder READER_STRING = new ScalarDecoder.StringReader(); /** * To resolve cyclic types, need to keep track of resolved named @@ -60,7 +58,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) case BYTES: if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); - return new BytesDecimalReader( + return new ScalarDecoder.BytesDecimalReader( decimal.getScale() ); } @@ -68,21 +66,21 @@ public ScalarDecoder createScalarValueDecoder(Schema type) case DOUBLE: return READER_DOUBLE; case ENUM: - return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); + return new ScalarDecoder.EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); case FIXED: if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); - return new FixedDecimalReader( + return new ScalarDecoder.FixedDecimalReader( decimal.getScale(), type.getFixedSize() ); } - return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); + return new ScalarDecoder.FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); case FLOAT: return READER_FLOAT; case INT: if (AvroSchemaHelper.getTypeId(type) != null) { - return new IntReader(AvroSchemaHelper.getTypeId(type)); + return new ScalarDecoder.IntReader(AvroSchemaHelper.getTypeId(type)); } return READER_INT; case LONG: @@ -91,7 +89,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) return READER_NULL; case STRING: if (AvroSchemaHelper.getTypeId(type) != null) { - return new StringReader(AvroSchemaHelper.getTypeId(type)); + return new ScalarDecoder.StringReader(AvroSchemaHelper.getTypeId(type)); } return READER_STRING; case UNION: @@ -110,7 +108,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) } readers[i++] = reader; } - return new ScalarUnionDecoder(readers); + return new ScalarDecoder.ScalarUnionDecoder(readers); } case ARRAY: // ok to call just can't handle case MAP: diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java new file mode 100644 index 000000000..fd00c14c8 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.UUID; + +public class AvroUUIDDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroUUIDDeserializer(); + @Override + public UUID deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + String value = jsonParser.getText(); + return UUID.fromString(value); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index eb3e9b102..9201099e9 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; @@ -206,27 +207,34 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) writerSchema = LogicalTypes.timeMicros() .addToSchema(Schema.create(Type.LONG)); } else { - JsonSerializer ser = null; + AvroUUID avroUUID = prop.getAnnotation(AvroUUID.class); - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + if(avroUUID != null) { + writerSchema = LogicalTypes.uuid() + .addToSchema(Schema.create(Type.STRING)); + } else { + JsonSerializer ser = null; + + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + } + ser = prov.findValueSerializer(prop.getType(), prop); } - ser = prov.findValueSerializer(prop.getType(), prop); + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); } } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java new file mode 100644 index 000000000..d9a0c2cbc --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMicrosSerializer extends JsonSerializer { + public static JsonSerializer INSTANCE = new AvroDateTimestampMicrosSerializer(); + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(date.getTime() * 1000L); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java new file mode 100644 index 000000000..0f3911612 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMillisSerializer extends JsonSerializer { + public static JsonSerializer INSTANCE = new AvroDateTimestampMillisSerializer(); + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(date.getTime()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java new file mode 100644 index 000000000..c2237044e --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.UUID; + +public class AvroUUIDSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new AvroUUIDSerializer(); + + @Override + public void serialize(UUID uuid, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(uuid.toString()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java deleted file mode 100644 index 0747e19ca..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Date; - -public class TimestampMillisecondSerializers { - public static final JsonSerializer LOCAL_DATE_TIME = new LocalDateTimeSerializer(); - public static final JsonSerializer DATE = new DateSerializer(); - public static final JsonSerializer ZONED_DATE_TIME = new ZonedDateTimeSerializer(); - public static final JsonSerializer OFFSET_DATE_TIME = new OffsetDateTimeSerializer(); - - static class DateSerializer extends JsonSerializer { - @Override - public void serialize(Date d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(d.getTime()); - } - } - - static class LocalDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(LocalDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant(ZoneOffset.UTC).toEpochMilli() - ); - } - } - - static class ZonedDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(ZonedDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant().toEpochMilli() - ); - } - } - - static class OffsetDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(OffsetDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant().toEpochMilli() - ); - } - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java index c7283592e..3736b8d1b 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java @@ -98,13 +98,13 @@ public void testSimplest() throws Exception public void testBinaryOk() throws Exception { ObjectMapper mapper = new ObjectMapper(new AvroFactory()); - Binary bin = new Binary("LocalDateTimeSerializer", new byte[] { 1, 2, 3, 4 }); + Binary bin = new Binary("Foo", new byte[] { 1, 2, 3, 4 }); byte[] bytes = mapper.writer(SCHEMA_WITH_BINARY_JSON).writeValueAsBytes(bin); assertEquals(9, bytes.length); assertNotNull(bytes); Binary output = mapper.reader(SCHEMA_WITH_BINARY_JSON).forType(Binary.class).readValue(bytes); assertNotNull(output); - assertEquals("LocalDateTimeSerializer", output.name); + assertEquals("Foo", output.name); assertNotNull(output.value); Assert.assertArrayEquals(bin.value, output.value); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java deleted file mode 100644 index 3f3965585..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import org.apache.avro.Schema; - -import java.util.Date; - -public class TimestampMillisTest extends LogicalTypeTestCase { - - static class RequiredTimestampMillis extends TestData { - @JsonProperty(required = true) - @AvroTimestampMillisecond - Date value; - - @Override - public Date value() { - return this.value; - } - } - - @Override - protected Class dataClass() { - return RequiredTimestampMillis.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-millis"; - } - - @Override - protected RequiredTimestampMillis testData() { - RequiredTimestampMillis v = new RequiredTimestampMillis(); - v.value = new Date(1526943920123L); - return v; - } - - @Override - protected Object convertedValue() { - return 1526943920123L; - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 14ebd663d..3de11d390 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -11,12 +11,15 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.junit.Assert; import java.math.BigDecimal; import java.util.Date; +import java.util.UUID; public class TestLogicalTypes extends AvroTestBase { @@ -68,6 +71,12 @@ static class DateType { public Date value; } + static class UUIDType { + @AvroUUID + @JsonProperty(required = true) + public UUID value; + } + AvroSchema getSchema(Class cls) throws JsonMappingException { AvroMapper avroMapper = new AvroMapper(); AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); @@ -152,4 +161,11 @@ public void testDateType() throws JsonMappingException { Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.INT, "date"); } + + public void testUUIDType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(UUIDType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.STRING, "uuid"); + } } diff --git a/pom.xml b/pom.xml index f9d5d07ca..e528c464c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ cbor smile avro + avro-java8 protobuf ion From f9c5c3b5c3e7a48859d82bf1735b7344d0dd7ab8 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 13:55:12 -0500 Subject: [PATCH 07/25] Cleaned up logic for converting dates. It's going to be lossy but that is how the official Avro project does it. --- .../java8/deser/BaseTimeJsonDeserializer.java | 14 +++++- .../java8/ser/BaseTimeJsonSerializer.java | 3 +- .../logicaltypes/TimestampMicrosTest.java | 50 ------------------- .../time/TimestampMicrosDateTest.java | 3 +- .../avro/AvroAnnotationIntrospector.java | 14 +++--- .../deser/AvroDateTimestampDeserializer.java | 40 +++++++++++++++ .../AvroDateTimestampMicrosDeserializer.java | 24 --------- .../AvroDateTimestampMillisDeserializer.java | 19 ------- .../AvroDateTimestampMicrosSerializer.java | 17 ------- .../AvroDateTimestampMillisSerializer.java | 17 ------- .../avro/ser/AvroDateTimestampSerializer.java | 39 +++++++++++++++ 11 files changed, 101 insertions(+), 139 deletions(-) delete mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java index 2e690b36c..f32d22316 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java @@ -22,7 +22,19 @@ public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { @Override public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { final long input = p.getLongValue(); - final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MICROSECONDS.toMillis(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } final Instant instant = Instant.ofEpochMilli(output); return fromInstant(instant); } diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java index 04ab716ba..bd660531c 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java @@ -25,8 +25,7 @@ public void serialize(T input, JsonGenerator jsonGenerator, SerializerProvider s final long output; switch (this.resolution) { case MICROSECONDS: - output = TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + - TimeUnit.NANOSECONDS.toMicros(instant.getNano()); + output = TimeUnit.MILLISECONDS.toMicros(instant.toEpochMilli()); break; case MILLISECONDS: output = instant.toEpochMilli(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java deleted file mode 100644 index 8fc368658..000000000 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import org.apache.avro.Schema; - -import java.util.Date; - -public class TimestampMicrosTest extends LogicalTypeTestCase { - - static class RequiredTimestampMicros extends TestData { - @JsonProperty(required = true) - @AvroTimestampMicrosecond - Date value; - - @Override - public Date value() { - return this.value; - } - } - - static final Date VALUE = new Date(1526943920123L); - - @Override - protected Class dataClass() { - return RequiredTimestampMicros.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-micros"; - } - - @Override - protected RequiredTimestampMicros testData() { - RequiredTimestampMicros v = new RequiredTimestampMicros(); - v.value = VALUE; - return v; - } - - @Override - protected Object convertedValue() { - return VALUE.getTime() * 1000L; - } -} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index 79a0cb266..93449ef82 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -7,6 +7,7 @@ import org.apache.avro.Schema; import java.util.Date; +import java.util.concurrent.TimeUnit; public class TimestampMicrosDateTest extends LogicalTypeTestCase { @Override @@ -35,7 +36,7 @@ protected TestCase testData() { @Override protected Object convertedValue() { - return 1526955327123L * 1000L; + return TimeUnit.MILLISECONDS.toMicros(VALUE.getTime()); } static class TestCase extends TestData { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index e6e5aed10..b3f5f3d23 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,12 +16,10 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; -import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMicrosDeserializer; -import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMillisDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; -import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMicrosSerializer; -import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMillisSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; import org.apache.avro.reflect.AvroAlias; @@ -92,13 +90,13 @@ public Object findDeserializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMillisDeserializer.INSTANCE; + return AvroDateTimestampDeserializer.MILLIS; } } AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); if (timestampMicrosecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMicrosDeserializer.INSTANCE; + return AvroDateTimestampDeserializer.MICROS; } } AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); @@ -168,13 +166,13 @@ public Object findSerializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMillisSerializer.INSTANCE; + return AvroDateTimestampSerializer.MILLIS; } } AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); if (timestampMicrosecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMicrosSerializer.INSTANCE; + return AvroDateTimestampSerializer.MICROS; } } AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java new file mode 100644 index 000000000..c0086420c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +public class AvroDateTimestampDeserializer extends JsonDeserializer { + public static final JsonDeserializer MILLIS = new AvroDateTimestampDeserializer(TimeUnit.MILLISECONDS); + public static final JsonDeserializer MICROS = new AvroDateTimestampDeserializer(TimeUnit.MICROSECONDS); + private final TimeUnit resolution; + + AvroDateTimestampDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MICROSECONDS.toMillis(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + return new Date(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java deleted file mode 100644 index bde804621..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.deser; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMicrosDeserializer extends JsonDeserializer { - public static JsonDeserializer INSTANCE = new AvroDateTimestampMicrosDeserializer(); - - @Override - public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - long value = jsonParser.getLongValue(); - - if (value == 0L) { - return new Date(value); - } - - return new Date(value / 1000L); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java deleted file mode 100644 index 53039802a..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.deser; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMillisDeserializer extends JsonDeserializer { - public static JsonDeserializer INSTANCE = new AvroDateTimestampMillisDeserializer(); - - @Override - public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - long value = jsonParser.getLongValue(); - return new Date(value); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java deleted file mode 100644 index d9a0c2cbc..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMicrosSerializer extends JsonSerializer { - public static JsonSerializer INSTANCE = new AvroDateTimestampMicrosSerializer(); - - @Override - public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(date.getTime() * 1000L); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java deleted file mode 100644 index 0f3911612..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMillisSerializer extends JsonSerializer { - public static JsonSerializer INSTANCE = new AvroDateTimestampMillisSerializer(); - - @Override - public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(date.getTime()); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java new file mode 100644 index 000000000..e40a42633 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java @@ -0,0 +1,39 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class AvroDateTimestampSerializer extends JsonSerializer { + public final static JsonSerializer MILLIS = new AvroDateTimestampSerializer(TimeUnit.MILLISECONDS); + public final static JsonSerializer MICROS = new AvroDateTimestampSerializer(TimeUnit.MICROSECONDS); + + private final TimeUnit resolution; + + AvroDateTimestampSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final long input = date.getTime(); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MILLISECONDS.toMicros(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + jsonGenerator.writeNumber(output); + } +} From 6e62edb6f37a3973f4af30205f0d93a43a6045df Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 15:34:18 -0500 Subject: [PATCH 08/25] Moved decimals to use serializers like the other types. --- .../AvroJavaTimeAnnotationIntrospector.java | 3 +- .../logicaltypes/time/DateLocalDateTest.java | 6 +-- .../time/TimeMicrosLocalTimeTest.java | 5 +-- .../time/TimeMillisLocalTimeTest.java | 4 +- .../time/TimestampMicrosDateTest.java | 4 +- .../TimestampMicrosLocalDateTimeTest.java | 10 ++--- .../TimestampMicrosOffsetDateTimeTest.java | 10 ++--- .../TimestampMicrosZonedDateTimeTest.java | 12 +++--- .../time/TimestampMillisDateTest.java | 4 +- .../TimestampMillisLocalDateTimeTest.java | 10 ++--- .../TimestampMillisOffsetDateTimeTest.java | 11 +++-- .../TimestampMillisZonedDateTimeTest.java | 10 ++--- .../avro/AvroAnnotationIntrospector.java | 26 ++++++++++++ .../avro/deser/AvroDecimalDeserializer.java | 24 +++++++++++ .../avro/ser/AvroBytesDecimalSerializer.java | 27 ++++++++++++ .../avro/ser/AvroFixedDecimalSerializer.java | 42 +++++++++++++++++++ .../avro/ser/NonBSGenericDatumWriter.java | 19 --------- 17 files changed, 161 insertions(+), 66 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java index 745b67a70..825dbaae5 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.dataformat.avro.AvroAnnotationIntrospector; import com.fasterxml.jackson.dataformat.avro.AvroDate; import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; @@ -25,7 +26,7 @@ import java.time.OffsetDateTime; import java.time.ZonedDateTime; -class AvroJavaTimeAnnotationIntrospector extends AnnotationIntrospector { +class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector { static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector(); @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java index fe5673aa1..38a2c3952 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -3,15 +3,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDate; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.LocalDate; -import java.util.Date; public class DateLocalDateTest extends LogicalTypeTestCase { + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -27,8 +27,6 @@ protected String logicalType() { return "date"; } - static final LocalDate VALUE = LocalDate.of(2011, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java index 8c010ef1e..ae0c9f825 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -12,6 +11,8 @@ import java.util.concurrent.TimeUnit; public class TimeMicrosLocalTimeTest extends LogicalTypeTestCase { + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -27,8 +28,6 @@ protected String logicalType() { return "time-micros"; } - static final LocalTime VALUE = LocalTime.of(3, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java index 77ca03b6e..af8b43b49 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -11,6 +11,8 @@ import java.util.concurrent.TimeUnit; public class TimeMillisLocalTimeTest extends LogicalTypeTestCase { + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -26,8 +28,6 @@ protected String logicalType() { return "time-millis"; } - static final LocalTime VALUE = LocalTime.of(3, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index 93449ef82..aac3b9246 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -10,6 +10,8 @@ import java.util.concurrent.TimeUnit; public class TimestampMicrosDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(1526955327123L); + @Override protected Class dataClass() { return TestCase.class; @@ -25,8 +27,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final Date VALUE = new Date(1526955327123L); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java index 0fc78f652..11d576d23 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -15,6 +15,11 @@ import java.util.concurrent.TimeUnit; public class TimestampMicrosLocalDateTimeTest extends LogicalTypeTestCase { + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -30,11 +35,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java index bafaea035..fd277c42f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZoneId; public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase { + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index c39af3fb8..f26296aaf 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -3,18 +3,21 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class TimestampMicrosZonedDateTimeTest extends LogicalTypeTestCase { + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -30,11 +33,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index 65f9d6444..c8117932a 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -10,6 +10,8 @@ import java.util.Date; public class TimestampMillisDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(1526955327123L); + @Override protected Class dataClass() { return TestCase.class; @@ -25,8 +27,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final Date VALUE = new Date(1526955327123L); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index 8e7526d91..ef4e617f8 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZoneId; public class TimestampMillisLocalDateTimeTest extends LogicalTypeTestCase { + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index b68002156..8a343467e 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -10,9 +10,13 @@ import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; -import java.time.ZonedDateTime; public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase { + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -28,11 +32,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java index aa7e13e66..f01f545ff 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZonedDateTime; public class TimestampMillisZonedDateTimeTest extends LogicalTypeTestCase { + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index b3f5f3d23..d1f184b17 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -17,9 +17,12 @@ import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDecimalDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; +import com.fasterxml.jackson.dataformat.avro.ser.AvroBytesDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroFixedDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; import org.apache.avro.reflect.AvroAlias; @@ -32,6 +35,7 @@ import org.apache.avro.reflect.Stringable; import org.apache.avro.reflect.Union; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -87,6 +91,13 @@ public Object findDeserializer(Annotated a) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); + if (decimal != null) { + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroDecimalDeserializer(decimal.scale()); + } + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -163,6 +174,21 @@ public Object findSerializer(Annotated a) { if (ann != null) { return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); + if (decimal != null) { + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + switch (decimal.schemaType()) { + case BYTES: + return new AvroBytesDecimalSerializer(decimal.scale()); + case FIXED: + return new AvroFixedDecimalSerializer(decimal.scale(), decimal.fixedSize()); + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported type for the logical type 'decimal'", decimal.schemaType()) + ); + } + } + } AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java new file mode 100644 index 000000000..91d0259c1 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java @@ -0,0 +1,24 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +public class AvroDecimalDeserializer extends JsonDeserializer { + private final int scale; + + public AvroDecimalDeserializer(int scale) { + this.scale = scale; + } + + @Override + public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final byte[] bytes = jsonParser.getBinaryValue(); + return new BigDecimal(new BigInteger(bytes), this.scale); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java new file mode 100644 index 000000000..37d6e9422 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.codehaus.jackson.JsonGenerationException; + +import java.io.IOException; +import java.math.BigDecimal; + +public class AvroBytesDecimalSerializer extends JsonSerializer { + final int scale; + + public AvroBytesDecimalSerializer(int scale) { + this.scale = scale; + } + + @Override + public void serialize(BigDecimal decimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (scale != decimal.scale()) { + throw new JsonGenerationException( + String.format("Cannot encode decimal with scale %s as scale %s.", decimal.scale(), scale) + ); + } + jsonGenerator.writeBinary(decimal.unscaledValue().toByteArray()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java new file mode 100644 index 000000000..e73ac83a5 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java @@ -0,0 +1,42 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.codehaus.jackson.JsonGenerationException; + +import java.io.IOException; +import java.math.BigDecimal; + +public class AvroFixedDecimalSerializer extends JsonSerializer { + final int scale; + final int fixedSize; + + public AvroFixedDecimalSerializer(int scale, int fixedSize) { + this.scale = scale; + this.fixedSize = fixedSize; + } + + @Override + public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (scale != value.scale()) { + throw new JsonGenerationException( + String.format("Cannot encode decimal with scale %s as scale %s.", value.scale(), scale) + ); + } + byte fillByte = (byte)(value.signum() < 0 ? 255 : 0); + byte[] unscaled = value.unscaledValue().toByteArray(); + byte[] bytes = new byte[this.fixedSize]; + int offset = bytes.length - unscaled.length; + + for(int i = 0; i < bytes.length; ++i) { + if (i < offset) { + bytes[i] = fillByte; + } else { + bytes[i] = unscaled[i - offset]; + } + } + + jsonGenerator.writeBinary(bytes); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index 90e3156aa..1bd39fa73 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -61,26 +61,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); return; case FIXED: - if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { - super.writeWithoutConversion( - schema, - DECIMAL_CONVERSION.toFixed(((BigDecimal) datum), schema, schema.getLogicalType()), - out - ); - return; - } - super.writeWithoutConversion(schema, datum, out); - return; case BYTES: - //TODO: This is ugly and I don't like the string check. - if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { - super.writeWithoutConversion( - schema, - DECIMAL_CONVERSION.toBytes(((BigDecimal) datum), schema, schema.getLogicalType()), - out - ); - return; - } super.writeWithoutConversion(schema, datum, out); return; case INT: From 7b55dafb1027317d438e2d0e32e489e4b47a4575 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 15:42:39 -0500 Subject: [PATCH 09/25] Accidentally added java8 dependencies. Reverted. --- avro/pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/avro/pom.xml b/avro/pom.xml index d44c0972c..1d9495a6d 100644 --- a/avro/pom.xml +++ b/avro/pom.xml @@ -60,10 +60,6 @@ abstractions. 2.5.0 test - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - @@ -78,14 +74,6 @@ abstractions. - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - From f260229e46299a1b2127373271e5bdff65272a2c Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 15:43:25 -0500 Subject: [PATCH 10/25] Removed direct avro dependency. We're already getting this from `jackson-dataformat-avro` --- avro-java8/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml index ee763eeb2..403e1d8b4 100644 --- a/avro-java8/pom.xml +++ b/avro-java8/pom.xml @@ -36,11 +36,6 @@ abstractions. jackson-dataformat-avro ${project.version} - - org.apache.avro - avro - 1.8.1 - From 817d80a34f9cdc3268f1e08baf522bb1301cd16d Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 17:09:02 -0500 Subject: [PATCH 11/25] Added support for java.util.Date for logical types `date`, `time-millis`, and `time-micros`. --- .../java8/logicaltypes/time/DateDateTest.java | 60 ++++++++++++++++++ .../logicaltypes/time/TimeMicrosDateTest.java | 62 ++++++++++++++++++ .../logicaltypes/time/TimeMillisDateTest.java | 63 +++++++++++++++++++ .../avro/AvroAnnotationIntrospector.java | 44 ++++++++++++- .../avro/deser/AvroDateDateDeserializer.java | 25 ++++++++ .../avro/deser/AvroDateTimeDeserializer.java | 36 +++++++++++ .../avro/ser/AvroDateDateSerializer.java | 29 +++++++++ .../avro/ser/AvroDateTimeSerializer.java | 40 ++++++++++++ 8 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java new file mode 100644 index 000000000..c5cc181a7 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class DateDateTest extends LogicalTypeTestCase { + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "date"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = new Date(TimeUnit.DAYS.toMillis(VALUE.toEpochDay())); + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toEpochDay(); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroDate + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java new file mode 100644 index 000000000..2348333fa --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java @@ -0,0 +1,62 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class TimeMicrosDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(8127123L); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + LocalTime time = VALUE.toInstant().atOffset(ZoneOffset.UTC).toLocalTime(); + return TimeUnit.NANOSECONDS.toMillis(time.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java new file mode 100644 index 000000000..f43da72ca --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class TimeMillisDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(8127123L); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + LocalTime time = VALUE.toInstant().atOffset(ZoneOffset.UTC).toLocalTime(); + return TimeUnit.NANOSECONDS.toMillis(time.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index d1f184b17..7e3b87922 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,11 +16,15 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateDateDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimeDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDecimalDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import com.fasterxml.jackson.dataformat.avro.ser.AvroBytesDecimalSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateDateSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimeSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroFixedDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; @@ -97,7 +101,18 @@ public Object findDeserializer(Annotated a) { return new AvroDecimalDeserializer(decimal.scale()); } } - + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (timeMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MILLIS; + } + } + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (timeMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MICROS; + } + } AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -116,7 +131,12 @@ public Object findDeserializer(Annotated a) { return AvroUUIDDeserializer.INSTANCE; } } - + AvroDate avroDate = _findAnnotation(a, AvroDate.class); + if (avroDate != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateDeserializer.INSTANCE; + } + } return null; } @@ -189,6 +209,19 @@ public Object findSerializer(Annotated a) { } } } + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (timeMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MILLIS; + } + } + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (timeMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MICROS; + } + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -207,7 +240,12 @@ public Object findSerializer(Annotated a) { return AvroUUIDSerializer.INSTANCE; } } - + AvroDate avroDate = _findAnnotation(a, AvroDate.class); + if (avroDate != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateSerializer.INSTANCE; + } + } return null; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java new file mode 100644 index 000000000..3b7df965c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java @@ -0,0 +1,25 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + + +public class AvroDateDateDeserializer extends JsonDeserializer { + public static final JsonDeserializer INSTANCE = new AvroDateDateDeserializer(); + private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; + + AvroDateDateDeserializer() { + + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + return new java.util.Date(input * MILLIS_PER_DAY); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java new file mode 100644 index 000000000..1efbc575e --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java @@ -0,0 +1,36 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +public class AvroDateTimeDeserializer extends JsonDeserializer { + public static final JsonDeserializer MILLIS = new AvroDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static final JsonDeserializer MICROS = new AvroDateTimeDeserializer(TimeUnit.MICROSECONDS); + private final TimeUnit resolution; + private final long max; + + AvroDateTimeDeserializer(TimeUnit resolution) { + this.resolution = resolution; + this.max = this.resolution.convert(86400, TimeUnit.SECONDS); + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + + if (input < 0 || input > this.max) { + throw new IllegalStateException( + String.format("Value must be between 0 and %s %s(s).", this.max, this.resolution) + ); + } + final long output = TimeUnit.MILLISECONDS.convert(input, this.resolution); + return new Date(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java new file mode 100644 index 000000000..ea8ef1adc --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class AvroDateDateSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new AvroDateDateSerializer(); + private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; + + @Override + public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + Calendar calendar = Calendar.getInstance(UTC); + calendar.setTime(value); + if (calendar.get(Calendar.HOUR_OF_DAY) != 0 || calendar.get(Calendar.MINUTE) != 0 || + calendar.get(Calendar.SECOND) != 0 || calendar.get(Calendar.MILLISECOND) != 0) { + throw new IllegalStateException("Date type should not have any time fields set to non-zero values."); + } + long unixMillis = calendar.getTimeInMillis(); + int output =(int) (unixMillis / MILLIS_PER_DAY); + jsonGenerator.writeNumber(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java new file mode 100644 index 000000000..bedf4afde --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class AvroDateTimeSerializer extends JsonSerializer { + public final static JsonSerializer MILLIS = new AvroDateTimeSerializer(TimeUnit.MILLISECONDS); + public final static JsonSerializer MICROS = new AvroDateTimeSerializer(TimeUnit.MICROSECONDS); + private final static Date MIN_DATE = new Date(0L); + private final static Date MAX_DATE = new Date( + TimeUnit.SECONDS.toMillis(86400) + ); + + private final TimeUnit resolution; + private final long max; + + + AvroDateTimeSerializer(TimeUnit resolution) { + this.resolution = resolution; + this.max = this.resolution.convert(86400, TimeUnit.SECONDS); + } + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final long input = date.getTime(); + + if (input < 0 || input > this.max) { + throw new IllegalStateException( + String.format("Value must be between %s and %s.", MIN_DATE, MAX_DATE) + ); + } + final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + jsonGenerator.writeNumber(output); + } +} From d4d13bab3ed7e79989fb82f64bb6ede8a55180f1 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Wed, 30 May 2018 17:54:31 -0700 Subject: [PATCH 12/25] Refactored to use a single attribute. Deprecated AvroFixedSize attribute for a single AvroType attribute that can handle all types. --- .../AvroJavaTimeAnnotationIntrospector.java | 176 ++++++++---------- .../java8/logicaltypes/BytesDecimalTest.java | 4 +- .../java8/logicaltypes/FixedDecimalTest.java | 4 +- .../avro/java8/logicaltypes/UUIDTest.java | 7 +- .../java8/logicaltypes/time/DateDateTest.java | 4 +- .../logicaltypes/time/DateLocalDateTest.java | 4 +- .../logicaltypes/time/TimeMicrosDateTest.java | 4 +- .../time/TimeMicrosLocalTimeTest.java | 4 +- .../logicaltypes/time/TimeMillisDateTest.java | 6 +- .../time/TimeMillisLocalTimeTest.java | 4 +- .../time/TimestampMicrosDateTest.java | 4 +- .../TimestampMicrosLocalDateTimeTest.java | 4 +- .../TimestampMicrosOffsetDateTimeTest.java | 4 +- .../TimestampMicrosZonedDateTimeTest.java | 4 +- .../time/TimestampMillisDateTest.java | 4 +- .../TimestampMillisLocalDateTimeTest.java | 5 +- .../TimestampMillisOffsetDateTimeTest.java | 4 +- .../TimestampMillisZonedDateTimeTest.java | 4 +- .../avro/AvroAnnotationIntrospector.java | 166 ++++++++--------- .../jackson/dataformat/avro/AvroDate.java | 18 -- .../jackson/dataformat/avro/AvroDecimal.java | 48 ----- .../dataformat/avro/AvroFixedSize.java | 1 + .../dataformat/avro/AvroTimeMicrosecond.java | 18 -- .../dataformat/avro/AvroTimeMillisecond.java | 18 -- .../avro/AvroTimestampMicrosecond.java | 18 -- .../avro/AvroTimestampMillisecond.java | 18 -- .../jackson/dataformat/avro/AvroType.java | 58 ++++++ .../jackson/dataformat/avro/AvroUUID.java | 19 -- .../dataformat/avro/schema/RecordVisitor.java | 162 ++++++++-------- .../avro/schema/TestLogicalTypes.java | 39 ++-- .../avro/schema/TestSimpleGeneration.java | 34 ++++ 31 files changed, 380 insertions(+), 487 deletions(-) delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java index 825dbaae5..ed3db6d78 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -1,14 +1,9 @@ package com.fasterxml.jackson.dataformat.avro.java8; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.dataformat.avro.AvroAnnotationIntrospector; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalTimeDeserializer; @@ -31,103 +26,94 @@ class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector { @Override public Object findSerializer(Annotated a) { - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (null != timestampMillisecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeSerializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MILLIS; - } - } - - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (null != timestampMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeSerializer.MICROS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MICROS; - } - } - - AvroDate date = _findAnnotation(a, AvroDate.class); - if (null != date) { - if (a.getRawType().isAssignableFrom(LocalDate.class)) { - return LocalDateSerializer.INSTANCE; - } - } - - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (null != timeMillisecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeSerializer.MILLIS; - } - } - - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (null != timeMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeSerializer.MICROS; + AvroType logicalType = _findAnnotation(a, AvroType.class); + if (null != logicalType) { + switch (logicalType.logicalType()) { + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MILLIS; + } + break; + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MICROS; + } + break; + case DATE: + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateSerializer.INSTANCE; + } + break; + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MILLIS; + } + break; + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MICROS; + } + break; } } return super.findSerializer(a); - } @Override public Object findDeserializer(Annotated a) { - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (null != timestampMillisecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeDeserializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MILLIS; - } - } - - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (null != timestampMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeDeserializer.MICROS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MICROS; - } - } - - AvroDate date = _findAnnotation(a, AvroDate.class); - if (null != date) { - if (a.getRawType().isAssignableFrom(LocalDate.class)) { - return LocalDateDeserializer.INSTANCE; - } - } - - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (null != timeMillisecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeDeserializer.MILLIS; - } - } - - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (null != timeMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeDeserializer.MICROS; + AvroType logicalType = _findAnnotation(a, AvroType.class); + if (null != logicalType) { + switch (logicalType.logicalType()) { + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MILLIS; + } + break; + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MICROS; + } + break; + case DATE: + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateDeserializer.INSTANCE; + } + break; + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MILLIS; + } + break; + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MICROS; + } + break; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java index 6b3fffce0..a9a92fca1 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Conversions; import org.apache.avro.Schema; @@ -39,7 +39,7 @@ protected Object convertedValue() { static class BytesDecimal extends TestData { @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3) + @AvroType(precision = 3, scale = 3, schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java index 410b9b606..a8c23c926 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Conversions; import org.apache.avro.Schema; @@ -39,7 +39,7 @@ protected Object convertedValue() { static class FixedDecimal extends TestData { @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + @AvroType(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java index 69c875d8f..6a54b0d90 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java @@ -1,12 +1,9 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; -import org.apache.avro.Conversions; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Schema; -import java.math.BigDecimal; import java.util.UUID; public class UUIDTest extends LogicalTypeTestCase { @@ -41,7 +38,7 @@ protected Object convertedValue() { static class UUIDTestCase extends TestData { @JsonProperty(required = true) - @AvroUUID + @AvroType(schemaType = Schema.Type.STRING, logicalType = AvroType.LogicalType.UUID) public UUID value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java index c5cc181a7..a490a65d0 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -48,7 +48,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java index 38a2c3952..a0079c368 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -46,7 +46,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) LocalDate value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java index 2348333fa..a5cb4061f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -50,7 +50,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java index ae0c9f825..92a2cb4bf 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -47,7 +47,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND) LocalTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java index f43da72ca..db22f1419 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java @@ -1,13 +1,12 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; -import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneOffset; import java.util.Date; @@ -51,7 +50,8 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) + Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java index af8b43b49..ec5fd4ac8 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -47,7 +47,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) LocalTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index aac3b9246..c94f9aa25 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -41,7 +41,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java index 11d576d23..bdd4dd54d 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -55,7 +55,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) LocalDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java index fd277c42f..8910a743c 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -46,7 +46,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) OffsetDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index f26296aaf..0dccaf944 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -52,7 +52,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) ZonedDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index c8117932a..efd80196f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -46,7 +46,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index ef4e617f8..0d44139e6 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -45,7 +45,8 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) + LocalDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index 8a343467e..db2e9ee8c 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -51,7 +51,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) OffsetDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java index f01f545ff..a3d4798e6 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -45,7 +45,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) ZonedDateTime value; @Override diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index 7e3b87922..b2f1055ee 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -95,46 +95,38 @@ public Object findDeserializer(Annotated a) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } - AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); - if (decimal != null) { - if (a.getRawType().isAssignableFrom(BigDecimal.class)) { - return new AvroDecimalDeserializer(decimal.scale()); - } - } - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (timeMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeDeserializer.MILLIS; - } - } - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (timeMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeDeserializer.MICROS; - } - } - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (timestampMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampDeserializer.MILLIS; - } - } - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (timestampMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampDeserializer.MICROS; - } - } - AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); - if (avroUUID != null) { - if (a.getRawType().isAssignableFrom(UUID.class)) { - return AvroUUIDDeserializer.INSTANCE; - } - } - AvroDate avroDate = _findAnnotation(a, AvroDate.class); - if (avroDate != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateDateDeserializer.INSTANCE; + AvroType logicalType = _findAnnotation(a, AvroType.class); + + if(null != logicalType) { + switch (logicalType.logicalType()) { + case DECIMAL: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroDecimalDeserializer(logicalType.scale()); + } + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MILLIS; + } + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampDeserializer.MILLIS; + } + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MICROS; + } + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampDeserializer.MICROS; + } + case UUID: + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDDeserializer.INSTANCE; + } + case DATE: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateDeserializer.INSTANCE; + } } } return null; @@ -194,58 +186,52 @@ public Object findSerializer(Annotated a) { if (ann != null) { return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } - AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); - if (decimal != null) { - if (a.getRawType().isAssignableFrom(BigDecimal.class)) { - switch (decimal.schemaType()) { - case BYTES: - return new AvroBytesDecimalSerializer(decimal.scale()); - case FIXED: - return new AvroFixedDecimalSerializer(decimal.scale(), decimal.fixedSize()); - default: - throw new UnsupportedOperationException( - String.format("%s is not a supported type for the logical type 'decimal'", decimal.schemaType()) - ); - } - } - } - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (timeMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeSerializer.MILLIS; - } - } - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (timeMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeSerializer.MICROS; - } - } - - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (timestampMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampSerializer.MILLIS; - } - } - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (timestampMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampSerializer.MICROS; - } - } - AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); - if (avroUUID != null) { - if (a.getRawType().isAssignableFrom(UUID.class)) { - return AvroUUIDSerializer.INSTANCE; - } - } - AvroDate avroDate = _findAnnotation(a, AvroDate.class); - if (avroDate != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateDateSerializer.INSTANCE; + AvroType logicalType = _findAnnotation(a, AvroType.class); + + if(null != logicalType) { + switch (logicalType.logicalType()) { + case DECIMAL: + switch (logicalType.schemaType()) { + case FIXED: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroFixedDecimalSerializer(logicalType.scale(), logicalType.fixedSize()); + } + case BYTES: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroBytesDecimalSerializer(logicalType.scale()); + } + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported type for the logical type 'decimal'", logicalType.schemaType()) + ); + } + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MILLIS; + } + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampSerializer.MILLIS; + } + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MICROS; + } + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampSerializer.MICROS; + } + case UUID: + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDSerializer.INSTANCE; + } + case DATE: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateSerializer.INSTANCE; + } } } + return null; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java deleted file mode 100644 index 0632781c1..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroDate { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java deleted file mode 100644 index d651ac9f8..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import org.apache.avro.Schema; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroDecimal { - /** - * - */ - Schema.Type schemaType() default Schema.Type.BYTES; - - /** - * The name of the type in the generated schema - */ - String typeName() default ""; - - /** - * The namespace of the type in the generated schema (optional) - */ - String typeNamespace() default ""; - - /** - * The size when the schemaType is FIXED. - */ - int fixedSize() default 0; - /** - * The maximum precision of decimals stored in this type. - */ - int precision(); - - /** - * - * @return - */ - int scale() default 0; -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java index 81210aa31..d5ed22b7f 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java @@ -10,6 +10,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) +@Deprecated public @interface AvroFixedSize { /** * The name of the type in the generated schema diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java deleted file mode 100644 index aa346d038..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimeMicrosecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java deleted file mode 100644 index e084f050b..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimeMillisecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java deleted file mode 100644 index 098391ebe..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimestampMicrosecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java deleted file mode 100644 index 414568241..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimestampMillisecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java new file mode 100644 index 000000000..53566ee76 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.dataformat.avro; + +import org.apache.avro.Schema; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroType { + /** + * + */ + Schema.Type schemaType(); + + /** + * + */ + LogicalType logicalType() default LogicalType.NONE; + + /** + * The name of the type in the generated schema + */ + String typeName() default ""; + + /** + * The namespace of the type in the generated schema (optional) + */ + String typeNamespace() default ""; + + /** + * The size when the schemaType is FIXED. + */ + int fixedSize() default 0; + /** + * The maximum precision of decimals stored in this type. + */ + int precision() default 0; + + /** + * + * @return + */ + int scale() default 0; + + enum LogicalType { + DECIMAL, + DATE, + TIME_MICROSECOND, + TIMESTAMP_MICROSECOND, + TIME_MILLISECOND, + TIMESTAMP_MILLISECOND, + UUID, + NONE + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java deleted file mode 100644 index 27acd79f5..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroUUID { -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index 9201099e9..3c2bb9ba5 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -5,13 +5,7 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; @@ -44,9 +38,9 @@ public class RecordVisitor protected final boolean _overridden; protected Schema _avroSchema; - + protected List _fields = new ArrayList(); - + public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas) { super(p); @@ -83,7 +77,7 @@ public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas } schemas.addSchema(type, _avroSchema); } - + @Override public Schema builtAvroSchema() { if (!_overridden) { @@ -98,7 +92,7 @@ public Schema builtAvroSchema() { /* JsonObjectFormatVisitor implementation /********************************************************** */ - + @Override public void property(BeanProperty writer) throws JsonMappingException { @@ -150,7 +144,7 @@ public void optionalProperty(String name, JsonFormatVisitable handler, /* Internal methods /********************************************************************** */ - + protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) throws JsonMappingException { Schema writerSchema; @@ -160,86 +154,80 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) Schema.Parser parser = new Schema.Parser(); writerSchema = parser.parse(schemaOverride.value()); } else { - AvroFixedSize fixedSize = prop.getAnnotation(AvroFixedSize.class); - if (fixedSize != null) { - writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); - } else { - AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); - if(decimal!= null) { - LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); - Schema s; - if(Type.BYTES == decimal.schemaType()) { - s = Schema.create(Type.BYTES); - } else if(Type.FIXED == decimal.schemaType()) { - s = Schema.createFixed(decimal.typeName(), - null, - decimal.typeNamespace(), - decimal.fixedSize() - ); - } else { - throw new IllegalStateException("Avro schema type must be BYTES or FIXED."); - } - writerSchema = d.addToSchema(s); - d.validate(writerSchema); + AvroType type = prop.getAnnotation(AvroType.class); + + if(null != type) { + if(type.schemaType() == Type.FIXED) { + writerSchema = Schema.createFixed(type.typeName(), + null, + type.typeNamespace(), + type.fixedSize() + ); } else { - AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); - if(timestampMillisecond != null) { + writerSchema = Schema.create(type.schemaType()); + } + switch (type.logicalType()) { + case NONE: + break; + case DATE: + writerSchema = LogicalTypes.date() + .addToSchema(writerSchema); + break; + case UUID: + writerSchema = LogicalTypes.uuid() + .addToSchema(writerSchema); + break; + case DECIMAL: + writerSchema = LogicalTypes.decimal(type.precision(), type.scale()) + .addToSchema(writerSchema); + break; + case TIME_MICROSECOND: + writerSchema = LogicalTypes.timeMicros() + .addToSchema(writerSchema); + break; + case TIME_MILLISECOND: + writerSchema = LogicalTypes.timeMillis() + .addToSchema(writerSchema); + break; + case TIMESTAMP_MICROSECOND: + writerSchema = LogicalTypes.timestampMicros() + .addToSchema(writerSchema); + break; + case TIMESTAMP_MILLISECOND: writerSchema = LogicalTypes.timestampMillis() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroTimestampMicrosecond timestampMicrosecond = prop.getAnnotation(AvroTimestampMicrosecond.class); - if(timestampMicrosecond!=null){ - writerSchema = LogicalTypes.timestampMicros() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroDate date = prop.getAnnotation(AvroDate.class); - if(date!=null){ - writerSchema = LogicalTypes.date() - .addToSchema(Schema.create(Type.INT)); - } else { - AvroTimeMillisecond timeMillisecond = prop.getAnnotation(AvroTimeMillisecond.class); - if(timeMillisecond!=null) { - writerSchema = LogicalTypes.timeMillis() - .addToSchema(Schema.create(Type.INT)); - } else { - AvroTimeMicrosecond timeMicrosecond = prop.getAnnotation(AvroTimeMicrosecond.class); - if(timeMicrosecond!=null) { - writerSchema = LogicalTypes.timeMicros() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroUUID avroUUID = prop.getAnnotation(AvroUUID.class); - - if(avroUUID != null) { - writerSchema = LogicalTypes.uuid() - .addToSchema(Schema.create(Type.STRING)); - } else { - JsonSerializer ser = null; + .addToSchema(writerSchema); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported logical type.", type.logicalType()) + ); + } + } else { + AvroFixedSize fixedSize = prop.getAnnotation(AvroFixedSize.class); + if (fixedSize != null) { + writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); + } else { + JsonSerializer ser = null; - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); - } - ser = prov.findValueSerializer(prop.getType(), prop); - } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); - } - } - } - } + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); } + ser = prov.findValueSerializer(prop.getType(), prop); } + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 3de11d390..faaac0ca9 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -2,17 +2,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import com.fasterxml.jackson.dataformat.avro.AvroTestBase; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.junit.Assert; @@ -25,61 +18,67 @@ public class TestLogicalTypes extends AvroTestBase { static class BytesDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5) + @AvroType(schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL, precision = 5) public BigDecimal value; } static class FixedNoNameDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED) + @AvroType(precision = 5, schemaType = Schema.Type.FIXED, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; } static class FixedDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED, typeName = "foo", typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", fixedSize = 8) + @AvroType(precision = 5, + schemaType = Schema.Type.FIXED, + typeName = "foo", + typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", + fixedSize = 8, + logicalType = AvroType.LogicalType.DECIMAL + ) public BigDecimal value; } static class TimestampMillisecondsType { - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) @JsonProperty(required = true) public Date value; } static class TimeMillisecondsType { - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) @JsonProperty(required = true) public Date value; } static class TimestampMicrosecondsType { - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) @JsonProperty(required = true) public Date value; } static class TimeMicrosecondsType { - @AvroTimeMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND) @JsonProperty(required = true) public Date value; } - + static class DateType { - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) @JsonProperty(required = true) public Date value; } static class UUIDType { - @AvroUUID + @AvroType(schemaType = Schema.Type.STRING, logicalType = AvroType.LogicalType.UUID) @JsonProperty(required = true) public UUID value; } AvroSchema getSchema(Class cls) throws JsonMappingException { AvroMapper avroMapper = new AvroMapper(); - AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); + AvroSchemaGenerator avroSchemaGenerator = new AvroSchemaGenerator(); avroMapper.acceptJsonFormatVisitor(cls, avroSchemaGenerator); AvroSchema schema = avroSchemaGenerator.getGeneratedSchema(); assertNotNull("Schema should not be null.", schema); @@ -154,7 +153,7 @@ public void testTimeMicrosecondsType() throws JsonMappingException { Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.LONG, "time-micros"); } - + public void testDateType() throws JsonMappingException { AvroSchema avroSchema = getSchema(DateType.class); Schema schema = avroSchema.getAvroSchema(); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java index ac8bf5e76..7c3b136cb 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java @@ -32,6 +32,27 @@ static class WithDate { public Date date; } + static class WithAvroTypeFixed { + @JsonProperty(required = true) + @AvroType(typeName = "FixedFieldBytes", fixedSize = 4, schemaType = Schema.Type.FIXED) + public byte[] fixedField; + + @JsonProperty(value = "wff", required = true) + @AvroType(typeName = "WrappedFixedFieldBytes", fixedSize = 8, schemaType = Schema.Type.FIXED) + public WithFixedField.WrappedByteArray wrappedFixedField; + + void setValue(byte[] bytes) { + this.fixedField = bytes; + } + + static class WrappedByteArray { + @JsonValue + public ByteBuffer getBytes() { + return null; + } + } + } + static class WithFixedField { @JsonProperty(required = true) @AvroFixedSize(typeName = "FixedFieldBytes", size = 4) @@ -159,6 +180,19 @@ public void testFixed() throws Exception assertEquals(8, wrappedFieldSchema.getFixedSize()); } + public void testFixedAvroType() throws Exception + { + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + MAPPER.acceptJsonFormatVisitor(WithAvroTypeFixed.class, gen); + Schema generated = gen.getAvroSchema(); + Schema fixedFieldSchema = generated.getField("fixedField").schema(); + assertEquals(Schema.Type.FIXED, fixedFieldSchema.getType()); + assertEquals(4, fixedFieldSchema.getFixedSize()); + + Schema wrappedFieldSchema = generated.getField("wff").schema(); + assertEquals(Schema.Type.FIXED, wrappedFieldSchema.getType()); + assertEquals(8, wrappedFieldSchema.getFixedSize()); + } // as per [dataformats-binary#98], no can do (unless we start supporting polymorphic // handling or something...) public void testSchemaForUntypedMap() throws Exception From 1f8c05b304daf69de152ac947d22570ef1f12ec4 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 8 May 2018 15:54:54 -0500 Subject: [PATCH 13/25] WIP Generation of avro schemas with logical types. --- .../jackson/dataformat/avro/AvroDate.java | 18 +++ .../jackson/dataformat/avro/AvroDecimal.java | 27 ++++ .../dataformat/avro/AvroTimeMicrosecond.java | 18 +++ .../dataformat/avro/AvroTimeMillisecond.java | 18 +++ .../avro/AvroTimestampMicrosecond.java | 18 +++ .../avro/AvroTimestampMillisecond.java | 18 +++ .../dataformat/avro/schema/RecordVisitor.java | 81 +++++++++--- .../avro/schema/TestLogicalTypes.java | 118 ++++++++++++++++++ 8 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java new file mode 100644 index 000000000..0632781c1 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroDate { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java new file mode 100644 index 000000000..379c7c9ed --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroDecimal { + /** + * The maximum precision of decimals stored in this type. + */ + int precision(); + + /** + * + * @return + */ + int scale() default 0; +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java new file mode 100644 index 000000000..aa346d038 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimeMicrosecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java new file mode 100644 index 000000000..e084f050b --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimeMillisecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java new file mode 100644 index 000000000..098391ebe --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimestampMicrosecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java new file mode 100644 index 000000000..414568241 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroTimestampMillisecond { + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index f1d6867bb..3f28cb5ac 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -5,6 +5,13 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; import org.apache.avro.reflect.AvroMeta; @@ -156,27 +163,65 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) if (fixedSize != null) { writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); } else { - JsonSerializer ser = null; + AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); + if(decimal!= null) { + LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); + writerSchema = d + .addToSchema(Schema.create(Type.BYTES)); + d.validate(writerSchema); + } else { + AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); + if(timestampMillisecond != null) { + writerSchema = LogicalTypes.timestampMillis() + .addToSchema(Schema.create(Type.LONG)); + } else { + AvroTimestampMicrosecond timestampMicrosecond = prop.getAnnotation(AvroTimestampMicrosecond.class); + if(timestampMicrosecond!=null){ + writerSchema = LogicalTypes.timestampMicros() + .addToSchema(Schema.create(Type.LONG)); + } else { + AvroDate date = prop.getAnnotation(AvroDate.class); + if(date!=null){ + writerSchema = LogicalTypes.date() + .addToSchema(Schema.create(Type.INT)); + } else { + AvroTimeMillisecond timeMillisecond = prop.getAnnotation(AvroTimeMillisecond.class); + if(timeMillisecond!=null) { + writerSchema = LogicalTypes.timeMillis() + .addToSchema(Schema.create(Type.INT)); + } else { + AvroTimeMicrosecond timeMicrosecond = prop.getAnnotation(AvroTimeMicrosecond.class); + if(timeMicrosecond!=null) { + writerSchema = LogicalTypes.timeMicros() + .addToSchema(Schema.create(Type.LONG)); + } else { + JsonSerializer ser = null; - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + } + ser = prov.findValueSerializer(prop.getType(), prop); + } + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); + } + } + } + } } - ser = prov.findValueSerializer(prop.getType(), prop); } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); } /* 23-Nov-2012, tatu: Actually let's also assume that primitive type values diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java new file mode 100644 index 000000000..71f3eba56 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -0,0 +1,118 @@ +package com.fasterxml.jackson.dataformat.avro.schema; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTestBase; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.Schema; + +import java.math.BigDecimal; +import java.util.Date; + +public class TestLogicalTypes extends AvroTestBase { + + static class DecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5) + public BigDecimal value; + } + + static class TimestampMillisecondsType { + @AvroTimestampMillisecond + @JsonProperty(required = true) + public Date value; + } + + static class TimeMillisecondsType { + @AvroTimeMillisecond + @JsonProperty(required = true) + public Date value; + } + + static class TimestampMicrosecondsType { + @AvroTimestampMicrosecond + @JsonProperty(required = true) + public Date value; + } + + static class TimeMicrosecondsType { + @AvroTimeMicrosecond + @JsonProperty(required = true) + public Date value; + } + + static class DateType { + @AvroDate + @JsonProperty(required = true) + public Date value; + } + + AvroSchema getSchema(Class cls) throws JsonMappingException { + AvroMapper avroMapper = new AvroMapper(); + AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); + avroMapper.acceptJsonFormatVisitor(cls, avroSchemaGenerator); + AvroSchema schema = avroSchemaGenerator.getGeneratedSchema(); + assertNotNull("Schema should not be null.", schema); + assertEquals(Schema.Type.RECORD, schema.getAvroSchema().getType()); + System.out.println(schema.getAvroSchema().toString(true)); + return schema; + } + + void assertLogicalType(Schema.Field field, final Schema.Type type, final String logicalType) { + assertEquals("schema().getType() does not match.", type, field.schema().getType()); + assertNotNull("logicalType should not be null.", field.schema().getLogicalType()); + assertEquals("logicalType does not match.", logicalType, field.schema().getLogicalType().getName()); + field.schema().getLogicalType().validate(field.schema()); + } + + public void testDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(DecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.BYTES, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + } + + public void testTimestampMillisecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimestampMillisecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "timestamp-millis"); + } + + public void testTimeMillisecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimeMillisecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.INT, "time-millis"); + } + + public void testTimestampMicrosecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimestampMicrosecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "timestamp-micros"); + } + + public void testTimeMicrosecondsType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(TimeMicrosecondsType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.LONG, "time-micros"); + } + + public void testDateType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(DateType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.INT, "date"); + } +} From b1660333f7937a6e4a7093a4d1201d8891e1f235 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 15 May 2018 17:03:42 -0500 Subject: [PATCH 14/25] Decimal schema generation is working. Serialization should be working as well. --- .../jackson/dataformat/avro/AvroDecimal.java | 21 ++++++ .../dataformat/avro/schema/RecordVisitor.java | 15 ++++- .../avro/ser/NonBSGenericDatumWriter.java | 67 +++++++++++++++++-- .../avro/logicaltypes/DecimalTest.java | 53 +++++++++++++++ .../avro/schema/TestLogicalTypes.java | 43 +++++++++++- 5 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java index 379c7c9ed..d651ac9f8 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.dataformat.avro; +import org.apache.avro.Schema; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,6 +16,25 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface AvroDecimal { + /** + * + */ + Schema.Type schemaType() default Schema.Type.BYTES; + + /** + * The name of the type in the generated schema + */ + String typeName() default ""; + + /** + * The namespace of the type in the generated schema (optional) + */ + String typeNamespace() default ""; + + /** + * The size when the schemaType is FIXED. + */ + int fixedSize() default 0; /** * The maximum precision of decimals stored in this type. */ diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index 3f28cb5ac..eb3e9b102 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -166,8 +166,19 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); if(decimal!= null) { LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); - writerSchema = d - .addToSchema(Schema.create(Type.BYTES)); + Schema s; + if(Type.BYTES == decimal.schemaType()) { + s = Schema.create(Type.BYTES); + } else if(Type.FIXED == decimal.schemaType()) { + s = Schema.createFixed(decimal.typeName(), + null, + decimal.typeNamespace(), + decimal.fixedSize() + ); + } else { + throw new IllegalStateException("Avro schema type must be BYTES or FIXED."); + } + writerSchema = d.addToSchema(s); d.validate(writerSchema); } else { AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index f7b87a99c..d7102748d 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -4,9 +4,14 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.apache.avro.Conversion; +import org.apache.avro.Conversions; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; +import org.apache.avro.data.TimeConversions; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.io.Encoder; @@ -20,14 +25,62 @@ public class NonBSGenericDatumWriter extends GenericDatumWriter { - private static final GenericData GENERIC_DATA = GenericData.get(); - private final static Class CLS_STRING = String.class; private final static Class CLS_BIG_DECIMAL = BigDecimal.class; private final static Class CLS_BIG_INTEGER = BigInteger.class; - + + private final GenericData genericData; + public NonBSGenericDatumWriter(Schema root) { - super(root); + super(root); + genericData = GenericData.get(); + + Map> conversions = new HashMap<>(); + if(Type.RECORD == root.getType()) { + for(Schema.Field field:root.getFields()) { + Schema fieldSchema; + + if(Type.UNION == field.schema().getType()) { + fieldSchema = AvroWriteContext.resolveUnionType(field.schema(), null); + } else { + fieldSchema = field.schema(); + } + if(null==fieldSchema.getLogicalType()) { + continue; + } + String logicalTypeName = fieldSchema.getLogicalType().getName(); + if(conversions.containsKey(logicalTypeName)){ + continue; + } + switch (logicalTypeName) { + case "decimal": + conversions.put(logicalTypeName, new Conversions.DecimalConversion()); + break; + case "date": + conversions.put(logicalTypeName, new TimeConversions.DateConversion()); + break; + case "time-millis": + conversions.put(logicalTypeName, new TimeConversions.TimeConversion()); + break; + case "time-micros": + conversions.put(logicalTypeName, new TimeConversions.TimeMicrosConversion()); + break; + case "timestamp-millis": + conversions.put(logicalTypeName, new TimeConversions.TimestampConversion()); + break; + case "timestamp-micros": + conversions.put(logicalTypeName, new TimeConversions.TimestampMicrosConversion()); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported logical type.", logicalTypeName) + ); + } + } + for(Conversion conversion: conversions.values()) { + genericData.addLogicalTypeConversion(conversion); + } + } } @Override @@ -55,7 +108,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio } break; case ENUM: - super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); + super.writeWithoutConversion(schema, genericData.createEnum(datum.toString(), schema), out); return; case INT: if (datum.getClass() == CLS_STRING) { @@ -98,7 +151,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio ((EncodedDatum) datum).write(out); return; } - super.writeWithoutConversion(schema, datum, out); -// super.write(schema, datum, out); +// super.writeWithoutConversion(schema, datum, out); + super.write(schema, datum, out); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java new file mode 100644 index 000000000..b7e56650b --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import junit.framework.TestCase; + +import java.io.IOException; +import java.math.BigDecimal; + +public class DecimalTest extends TestCase { + static class RequiredDecimal { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + } + + static class OptionalDecimal { + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + } + + public void testRequired() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final RequiredDecimal expected = new RequiredDecimal(); + expected.value = BigDecimal.valueOf(123456, 3); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final RequiredDecimal actual = mapper.reader(avroSchema).forType(RequiredDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testOptional() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(OptionalDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final OptionalDecimal expected = new OptionalDecimal(); + expected.value = BigDecimal.valueOf(123456, 3); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalDecimal actual = mapper.reader(avroSchema).forType(OptionalDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 71f3eba56..14ebd663d 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -12,18 +12,32 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import org.apache.avro.Schema; +import org.apache.avro.SchemaParseException; +import org.junit.Assert; import java.math.BigDecimal; import java.util.Date; public class TestLogicalTypes extends AvroTestBase { - static class DecimalType { + static class BytesDecimalType { @JsonProperty(required = true) @AvroDecimal(precision = 5) public BigDecimal value; } + static class FixedNoNameDecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED) + public BigDecimal value; + } + + static class FixedDecimalType { + @JsonProperty(required = true) + @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED, typeName = "foo", typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", fixedSize = 8) + public BigDecimal value; + } + static class TimestampMillisecondsType { @AvroTimestampMillisecond @JsonProperty(required = true) @@ -72,8 +86,22 @@ void assertLogicalType(Schema.Field field, final Schema.Type type, final String field.schema().getLogicalType().validate(field.schema()); } - public void testDecimalType() throws JsonMappingException { - AvroSchema avroSchema = getSchema(DecimalType.class); + public void testFixedNoNameDecimalType() throws JsonMappingException { + try { + AvroSchema avroSchema = getSchema(FixedNoNameDecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.BYTES, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + Assert.fail("SchemaParseException should have been thrown"); + } catch (SchemaParseException ex) { + + } + } + + public void testBytesDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(BytesDecimalType.class); Schema schema = avroSchema.getAvroSchema(); Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.BYTES, "decimal"); @@ -81,6 +109,15 @@ public void testDecimalType() throws JsonMappingException { assertEquals(0, field.schema().getObjectProp("scale")); } + public void testFixedDecimalType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(FixedDecimalType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.FIXED, "decimal"); + assertEquals(5, field.schema().getObjectProp("precision")); + assertEquals(0, field.schema().getObjectProp("scale")); + } + public void testTimestampMillisecondsType() throws JsonMappingException { AvroSchema avroSchema = getSchema(TimestampMillisecondsType.class); Schema schema = avroSchema.getAvroSchema(); From 7cfa079a135431083dc76b6741f2e92948df0344 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Mon, 21 May 2018 17:57:09 -0500 Subject: [PATCH 15/25] Working using the JacksonAvroParserImpl. Both fixed and byte based decimals are working. --- .../avro/apacheimpl/ApacheAvroParserImpl.java | 20 ++++ .../dataformat/avro/deser/AvroParserImpl.java | 11 +++ .../avro/deser/AvroReaderFactory.java | 18 +++- .../avro/deser/JacksonAvroParserImpl.java | 28 ++++++ .../dataformat/avro/deser/ScalarDecoder.java | 97 ++++++++++++++++++ .../avro/ser/NonBSGenericDatumWriter.java | 99 +++++++------------ .../avro/logicaltypes/DecimalTest.java | 72 +++++++++++--- 7 files changed, 268 insertions(+), 77 deletions(-) diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java index 97af3aed4..0f187e97a 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java @@ -372,6 +372,26 @@ public int decodeEnum() throws IOException { return (_enumIndex = _decoder.readEnum()); } + @Override + public JsonToken decodeBytesDecimal(int scale) throws IOException { + return null; + } + + @Override + public void skipBytesDecimal() throws IOException { + + } + + @Override + public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { + return null; + } + + @Override + public void skipFixedDecimal(int size) throws IOException { + + } + /* /********************************************************** /* Methods for AvroReadContext impls, other diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java index 5e045dea8..a5573c1a7 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java @@ -560,6 +560,17 @@ public long getRemainingElements() public abstract int decodeIndex() throws IOException; public abstract int decodeEnum() throws IOException; + /* + /********************************************************** + /* Methods for AvroReadContext implementations: decimals + /********************************************************** + */ + + public abstract JsonToken decodeBytesDecimal(int scale) throws IOException; + public abstract void skipBytesDecimal() throws IOException; + public abstract JsonToken decodeFixedDecimal(int scale, int size) throws IOException; + public abstract void skipFixedDecimal(int size) throws IOException; + /* /********************************************************** /* Methods for AvroReadContext impls, other diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java index 517e7fc2c..50b21176c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java @@ -2,6 +2,7 @@ import java.util.*; +import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.util.internal.JacksonUtils; @@ -56,13 +57,26 @@ public ScalarDecoder createScalarValueDecoder(Schema type) switch (type.getType()) { case BOOLEAN: return READER_BOOLEAN; - case BYTES: + case BYTES: + if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { + LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); + return new BytesDecimalReader( + decimal.getScale() + ); + } return READER_BYTES; case DOUBLE: return READER_DOUBLE; case ENUM: return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); - case FIXED: + case FIXED: + if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { + LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); + return new FixedDecimalReader( + decimal.getScale(), + type.getFixedSize() + ); + } return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); case FLOAT: return READER_FLOAT; diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java index c4954845b..7dcca673c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java @@ -4,6 +4,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.io.IOContext; @@ -993,6 +995,32 @@ public int decodeEnum() throws IOException { return (_enumIndex = decodeInt()); } + @Override + public JsonToken decodeBytesDecimal(int scale) throws IOException { + decodeBytes(); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; + } + + @Override + public void skipBytesDecimal() throws IOException { + skipBytes(); + } + + @Override + public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { + decodeFixed(size); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; + } + + @Override + public void skipFixedDecimal(int size) throws IOException { + skipFixed(size); + } + @Override public boolean checkInputEnd() throws IOException { if (_closed) { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java index 55e67bd93..d8516fd15 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ScalarDecoder.java @@ -1,6 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.deser; import java.io.IOException; +import java.math.BigDecimal; import java.util.List; import com.fasterxml.jackson.core.JsonToken; @@ -546,4 +547,100 @@ public void skipValue(AvroParserImpl parser) throws IOException { } } } + + protected final static class FixedDecimalReader extends ScalarDecoder { + private final int _scale; + private final int _size; + + public FixedDecimalReader(int scale, int size) { + _scale = scale; + _size = size; + } + + @Override + public JsonToken decodeValue(AvroParserImpl parser) throws IOException { + return parser.decodeFixedDecimal(_scale, _size); + } + + @Override + protected void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFixedDecimal(_size); + } + + @Override + public String getTypeId() { + return AvroSchemaHelper.getTypeId(BigDecimal.class); + } + + @Override + public AvroFieldReader asFieldReader(String name, boolean skipper) { + return new FR(name, skipper, getTypeId(), _scale, _size); + } + + private final static class FR extends AvroFieldReader { + private final int _scale; + private final int _size; + public FR(String name, boolean skipper, String typeId, int scale, int size) { + super(name, skipper, typeId); + _scale = scale; + _size = size; + } + + @Override + public JsonToken readValue(AvroReadContext parent, AvroParserImpl parser) throws IOException { + return parser.decodeFixedDecimal(_scale, _size); + } + + @Override + public void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFixedDecimal(_size); + } + } + } + + protected final static class BytesDecimalReader extends ScalarDecoder { + private final int _scale; + + public BytesDecimalReader(int scale) { + _scale = scale; + } + + @Override + public JsonToken decodeValue(AvroParserImpl parser) throws IOException { + return parser.decodeBytesDecimal(_scale); + } + + @Override + protected void skipValue(AvroParserImpl parser) throws IOException { + parser.skipBytesDecimal(); + } + + @Override + public String getTypeId() { + return AvroSchemaHelper.getTypeId(BigDecimal.class); + } + + @Override + public AvroFieldReader asFieldReader(String name, boolean skipper) { + return new FR(name, skipper, getTypeId(), _scale); + } + + private final static class FR extends AvroFieldReader { + private final int _scale; + public FR(String name, boolean skipper, String typeId, int scale) { + super(name, skipper, typeId); + _scale = scale; + } + + @Override + public JsonToken readValue(AvroReadContext parent, AvroParserImpl parser) throws IOException { + return parser.decodeBytesDecimal(_scale); + } + + @Override + public void skipValue(AvroParserImpl parser) throws IOException { + parser.skipFloat(); + } + } + } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index d7102748d..90e3156aa 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -1,86 +1,36 @@ package com.fasterxml.jackson.dataformat.avro.ser; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.apache.avro.Conversion; import org.apache.avro.Conversions; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; -import org.apache.avro.data.TimeConversions; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.io.Encoder; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; + /** * Need to sub-class to prevent encoder from crapping on writing an optional * Enum value (see [dataformat-avro#12]) - * + * * @since 2.5 */ public class NonBSGenericDatumWriter extends GenericDatumWriter { + private static final GenericData GENERIC_DATA = GenericData.get(); + private final static Class CLS_STRING = String.class; private final static Class CLS_BIG_DECIMAL = BigDecimal.class; private final static Class CLS_BIG_INTEGER = BigInteger.class; + private final static Conversions.DecimalConversion DECIMAL_CONVERSION = new Conversions.DecimalConversion(); - private final GenericData genericData; public NonBSGenericDatumWriter(Schema root) { - super(root); - genericData = GenericData.get(); - - Map> conversions = new HashMap<>(); - if(Type.RECORD == root.getType()) { - for(Schema.Field field:root.getFields()) { - Schema fieldSchema; - - if(Type.UNION == field.schema().getType()) { - fieldSchema = AvroWriteContext.resolveUnionType(field.schema(), null); - } else { - fieldSchema = field.schema(); - } - if(null==fieldSchema.getLogicalType()) { - continue; - } - String logicalTypeName = fieldSchema.getLogicalType().getName(); - if(conversions.containsKey(logicalTypeName)){ - continue; - } - switch (logicalTypeName) { - case "decimal": - conversions.put(logicalTypeName, new Conversions.DecimalConversion()); - break; - case "date": - conversions.put(logicalTypeName, new TimeConversions.DateConversion()); - break; - case "time-millis": - conversions.put(logicalTypeName, new TimeConversions.TimeConversion()); - break; - case "time-micros": - conversions.put(logicalTypeName, new TimeConversions.TimeMicrosConversion()); - break; - case "timestamp-millis": - conversions.put(logicalTypeName, new TimeConversions.TimestampConversion()); - break; - case "timestamp-micros": - conversions.put(logicalTypeName, new TimeConversions.TimestampMicrosConversion()); - break; - default: - throw new UnsupportedOperationException( - String.format("%s is not a supported logical type.", logicalTypeName) - ); - } - } - for(Conversion conversion: conversions.values()) { - genericData.addLogicalTypeConversion(conversion); - } - } + super(root); } @Override @@ -108,7 +58,30 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio } break; case ENUM: - super.writeWithoutConversion(schema, genericData.createEnum(datum.toString(), schema), out); + super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); + return; + case FIXED: + if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { + super.writeWithoutConversion( + schema, + DECIMAL_CONVERSION.toFixed(((BigDecimal) datum), schema, schema.getLogicalType()), + out + ); + return; + } + super.writeWithoutConversion(schema, datum, out); + return; + case BYTES: + //TODO: This is ugly and I don't like the string check. + if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { + super.writeWithoutConversion( + schema, + DECIMAL_CONVERSION.toBytes(((BigDecimal) datum), schema, schema.getLogicalType()), + out + ); + return; + } + super.writeWithoutConversion(schema, datum, out); return; case INT: if (datum.getClass() == CLS_STRING) { @@ -151,7 +124,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio ((EncodedDatum) datum).write(out); return; } -// super.writeWithoutConversion(schema, datum, out); - super.write(schema, datum, out); + super.writeWithoutConversion(schema, datum, out); +// super.write(schema, datum, out); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java index b7e56650b..22549dfe6 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java @@ -5,47 +5,95 @@ import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import junit.framework.TestCase; +import org.apache.avro.Schema; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericRecord; import java.io.IOException; import java.math.BigDecimal; public class DecimalTest extends TestCase { - static class RequiredDecimal { + static class RequiredBytesDecimal { @JsonProperty(required = true) @AvroDecimal(precision = 3, scale = 3) public BigDecimal value; } - static class OptionalDecimal { + static class OptionalBytesDecimal { @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; + public BigDecimal value; + } + + static class RequiredFixedDecimal { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; + } + + static class OptionalFixedDecimal { + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; } - - public void testRequired() throws IOException { + + public void testRequiredBytesDecimal() throws IOException { final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredDecimal.class); + final AvroSchema avroSchema = mapper.schemaFor(RequiredBytesDecimal.class); System.out.println(avroSchema.getAvroSchema().toString(true)); - final RequiredDecimal expected = new RequiredDecimal(); + final RequiredBytesDecimal expected = new RequiredBytesDecimal(); expected.value = BigDecimal.valueOf(123456, 3); byte[] buffer = mapper.writer(avroSchema) .writeValueAsBytes(expected); - final RequiredDecimal actual = mapper.reader(avroSchema).forType(RequiredDecimal.class) + final RequiredBytesDecimal actual = mapper.reader(avroSchema).forType(RequiredBytesDecimal.class) .readValue(buffer); assertNotNull(actual); assertEquals(expected.value, actual.value); } - public void testOptional() throws IOException { + public void testOptionalBytesDecimal() throws IOException { final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalDecimal.class); + final AvroSchema avroSchema = mapper.schemaFor(OptionalBytesDecimal.class); System.out.println(avroSchema.getAvroSchema().toString(true)); - final OptionalDecimal expected = new OptionalDecimal(); + final OptionalBytesDecimal expected = new OptionalBytesDecimal(); + expected.value = BigDecimal.valueOf(9834780979L, 3); + final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); + expectedRecord.put("value", expected.value); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalBytesDecimal actual = mapper.reader(avroSchema).forType(OptionalBytesDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testRequiredFixedDecimal() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredFixedDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final RequiredFixedDecimal expected = new RequiredFixedDecimal(); expected.value = BigDecimal.valueOf(123456, 3); byte[] buffer = mapper.writer(avroSchema) .writeValueAsBytes(expected); - final OptionalDecimal actual = mapper.reader(avroSchema).forType(OptionalDecimal.class) + final RequiredFixedDecimal actual = mapper.reader(avroSchema).forType(RequiredFixedDecimal.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + + public void testOptionalFixedDecimal() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(OptionalFixedDecimal.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + + final OptionalFixedDecimal expected = new OptionalFixedDecimal(); + expected.value = BigDecimal.valueOf(9834780979L, 3); + final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); + expectedRecord.put("value", expected.value); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final OptionalFixedDecimal actual = mapper.reader(avroSchema).forType(OptionalFixedDecimal.class) .readValue(buffer); assertNotNull(actual); assertEquals(expected.value, actual.value); From b5706c3f04a2b9582eeb6282bd5bd5f13ad33f4e Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Mon, 21 May 2018 19:16:20 -0500 Subject: [PATCH 16/25] Restructuring of the test cases. --- .../avro/logicaltypes/BytesDecimalTest.java | 50 +++++++++ .../avro/logicaltypes/DecimalTest.java | 101 ------------------ .../avro/logicaltypes/FixedDecimalTest.java | 51 +++++++++ .../logicaltypes/LogicalTypeTestCase.java | 91 ++++++++++++++++ .../avro/logicaltypes/TestData.java | 5 + .../logicaltypes/TimestampMicrosTest.java | 55 ++++++++++ .../logicaltypes/TimestampMillisTest.java | 49 +++++++++ .../logicaltypes/TimestampMillisTestOld.java | 36 +++++++ 8 files changed, 337 insertions(+), 101 deletions(-) create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java new file mode 100644 index 000000000..b48edc30f --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java @@ -0,0 +1,50 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; + +public class BytesDecimalTest extends LogicalTypeTestCase { + static final BigDecimal VALUE = BigDecimal.valueOf(123456, 3); + + @Override + protected Class dataClass() { + return BytesDecimal.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.BYTES; + } + + @Override + protected String logicalType() { + return "decimal"; + } + + @Override + protected BytesDecimal testData() { + BytesDecimal v = new BytesDecimal(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return new Conversions.DecimalConversion().toBytes(VALUE, this.schema, this.schema.getLogicalType()); + } + + static class BytesDecimal extends TestData { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3) + public BigDecimal value; + + @Override + BigDecimal value() { + return this.value; + } + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java deleted file mode 100644 index 22549dfe6..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/DecimalTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; -import junit.framework.TestCase; -import org.apache.avro.Schema; -import org.apache.avro.generic.GenericData; -import org.apache.avro.generic.GenericRecord; - -import java.io.IOException; -import java.math.BigDecimal; - -public class DecimalTest extends TestCase { - static class RequiredBytesDecimal { - @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; - } - - static class OptionalBytesDecimal { - @AvroDecimal(precision = 3, scale = 3) - public BigDecimal value; - } - - static class RequiredFixedDecimal { - @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) - public BigDecimal value; - } - - static class OptionalFixedDecimal { - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) - public BigDecimal value; - } - - public void testRequiredBytesDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredBytesDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final RequiredBytesDecimal expected = new RequiredBytesDecimal(); - expected.value = BigDecimal.valueOf(123456, 3); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredBytesDecimal actual = mapper.reader(avroSchema).forType(RequiredBytesDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testOptionalBytesDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalBytesDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final OptionalBytesDecimal expected = new OptionalBytesDecimal(); - expected.value = BigDecimal.valueOf(9834780979L, 3); - final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); - expectedRecord.put("value", expected.value); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final OptionalBytesDecimal actual = mapper.reader(avroSchema).forType(OptionalBytesDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testRequiredFixedDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredFixedDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final RequiredFixedDecimal expected = new RequiredFixedDecimal(); - expected.value = BigDecimal.valueOf(123456, 3); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredFixedDecimal actual = mapper.reader(avroSchema).forType(RequiredFixedDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - - public void testOptionalFixedDecimal() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(OptionalFixedDecimal.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - - final OptionalFixedDecimal expected = new OptionalFixedDecimal(); - expected.value = BigDecimal.valueOf(9834780979L, 3); - final GenericRecord expectedRecord = new GenericData.Record(avroSchema.getAvroSchema()); - expectedRecord.put("value", expected.value); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final OptionalFixedDecimal actual = mapper.reader(avroSchema).forType(OptionalFixedDecimal.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java new file mode 100644 index 000000000..11343f8b1 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java @@ -0,0 +1,51 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; + +public class FixedDecimalTest extends LogicalTypeTestCase { + static final BigDecimal VALUE = BigDecimal.valueOf(123456, 3); + + @Override + protected Class dataClass() { + return FixedDecimal.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.FIXED; + } + + @Override + protected String logicalType() { + return "decimal"; + } + + @Override + protected FixedDecimal testData() { + FixedDecimal v = new FixedDecimal(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return new Conversions.DecimalConversion().toFixed(VALUE, this.schema, this.schema.getLogicalType()); + } + + static class FixedDecimal extends TestData { + @JsonProperty(required = true) + @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + public BigDecimal value; + + @Override + BigDecimal value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java new file mode 100644 index 000000000..4f2bc3563 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java @@ -0,0 +1,91 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import junit.framework.TestCase; +import org.apache.avro.Schema; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.io.BinaryEncoder; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.EncoderFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public abstract class LogicalTypeTestCase extends TestCase { + protected AvroMapper mapper; + protected AvroSchema avroSchema; + protected Schema recordSchema; + protected Schema schema; + protected Class dataClass; + protected Schema.Type schemaType; + protected String logicalType; + + protected abstract Class dataClass(); + protected abstract Schema.Type schemaType(); + protected abstract String logicalType(); + protected abstract T testData(); + protected abstract Object convertedValue(); + + + @Override + protected void setUp() throws Exception { + this.mapper = new AvroMapper(); + this.dataClass = dataClass(); + + this.avroSchema = mapper.schemaFor(this.dataClass); + assertNotNull("AvroSchema should not be null", this.avroSchema); + this.recordSchema = this.avroSchema.getAvroSchema(); + assertNotNull("Schema should not be null", this.recordSchema); + assertEquals("Schema should be a record.", Schema.Type.RECORD, this.recordSchema.getType()); + Schema.Field field = this.recordSchema.getField("value"); + assertNotNull("schema must have a 'value' field", field); + this.schema = field.schema(); + this.schemaType = schemaType(); + this.logicalType = logicalType(); + + System.out.println(recordSchema.toString(true)); + } + + public void testSchemaType() { + assertEquals("schema.getType() does not match.", this.schemaType, this.schema.getType()); + } + + public void testLogicalType() { + assertNotNull("schema.getLogicalType() should not return null",this.schema.getLogicalType()); + assertEquals("schema.getLogicalType().getName() does not match.",this.logicalType, this.schema.getLogicalType().getName()); + } + + byte[] serialize(T expected) throws JsonProcessingException { + final byte[] actualbytes = this.mapper.writer(this.avroSchema).writeValueAsBytes(expected); + return actualbytes; + } + + public void testRoundTrip() throws IOException { + final T expected = testData(); + final byte[] actualbytes = serialize(expected); + final T actual = this.mapper.reader(avroSchema).forType(this.dataClass).readValue(actualbytes); + assertNotNull("actual should not be null.", actual); + assertEquals(expected.value(), actual.value()); + } + + public void testAvroSerialization() throws IOException { + final T expected = testData(); + final byte[] actualbytes = serialize(expected); + final Object convertedValue = convertedValue(); + byte[] expectedBytes; + try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(outputStream, null); + GenericData.Record record = new GenericData.Record(this.recordSchema); + record.put("value", convertedValue); + DatumWriter writer = new GenericDatumWriter(this.recordSchema); + writer.write(record, encoder); + expectedBytes = outputStream.toByteArray(); + } + + assertTrue("serialized output does not match avro version.", Arrays.equals(expectedBytes, actualbytes)); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java new file mode 100644 index 000000000..66aa7b2b1 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java @@ -0,0 +1,5 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +abstract class TestData { + abstract T value(); +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java new file mode 100644 index 000000000..ea14c161c --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java @@ -0,0 +1,55 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import junit.framework.TestCase; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; +import org.apache.avro.data.TimeConversions; + +import java.io.IOException; +import java.util.Date; + +public class TimestampMicrosTest extends LogicalTypeTestCase { + + static class RequiredTimestampMicros extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + + @Override + protected Class dataClass() { + return RequiredTimestampMicros.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + @Override + protected RequiredTimestampMicros testData() { + RequiredTimestampMicros v = new RequiredTimestampMicros(); + v.value = new Date(1526943920123L); + return v; + } + + @Override + protected Object convertedValue() { + return 1526943920123L * 1000L; + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java new file mode 100644 index 000000000..3f3965585 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java @@ -0,0 +1,49 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import org.apache.avro.Schema; + +import java.util.Date; + +public class TimestampMillisTest extends LogicalTypeTestCase { + + static class RequiredTimestampMillis extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + + @Override + protected Class dataClass() { + return RequiredTimestampMillis.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + @Override + protected RequiredTimestampMillis testData() { + RequiredTimestampMillis v = new RequiredTimestampMillis(); + v.value = new Date(1526943920123L); + return v; + } + + @Override + protected Object convertedValue() { + return 1526943920123L; + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java new file mode 100644 index 000000000..0f917fb9e --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java @@ -0,0 +1,36 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Date; + +public class TimestampMillisTestOld extends TestCase { + + static class RequiredTimestampMillis { + @JsonProperty(required = true) + @AvroTimestampMillisecond + public Date value; + } + + + public void testRoundtrip() throws IOException { + final AvroMapper mapper = new AvroMapper(); + final AvroSchema avroSchema = mapper.schemaFor(RequiredTimestampMillis.class); + System.out.println(avroSchema.getAvroSchema().toString(true)); + final RequiredTimestampMillis expected = new RequiredTimestampMillis(); + expected.value = new Date(1526943920123L); + byte[] buffer = mapper.writer(avroSchema) + .writeValueAsBytes(expected); + final RequiredTimestampMillis actual = mapper.reader(avroSchema).forType(RequiredTimestampMillis.class) + .readValue(buffer); + assertNotNull(actual); + assertEquals(expected.value, actual.value); + } + +} From 39c689f57823acaecb7106934b99c2445b827cf5 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Tue, 22 May 2018 13:30:31 -0500 Subject: [PATCH 17/25] Added more around logicalTypes and java.time. --- .../avro/AvroAnnotationIntrospector.java | 344 ++++++++++-------- .../dataformat/avro/AvroMicroTimeModule.java | 39 ++ .../avro/apacheimpl/ApacheAvroParserImpl.java | 16 +- .../ser/TimestampMillisecondSerializers.java | 53 +++ .../dataformat/avro/SimpleGenerationTest.java | 4 +- .../avro/logicaltypes/BytesDecimalTest.java | 2 +- .../avro/logicaltypes/FixedDecimalTest.java | 2 +- .../logicaltypes/LogicalTypeTestCase.java | 6 + .../avro/logicaltypes/TestData.java | 4 +- .../logicaltypes/TimestampMicrosTest.java | 6 +- .../logicaltypes/TimestampMillisTestOld.java | 36 -- .../logicaltypes/time/TimeMillisDateTest.java | 63 ++++ .../time/TimeMillisLocalDateTimeTest.java | 65 ++++ .../time/TimeMillisOffsetDateTimeTest.java | 66 ++++ .../time/TimeMillisZonedDateTimeTest.java | 65 ++++ 15 files changed, 564 insertions(+), 207 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index 6415a25f3..fa596af97 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -1,11 +1,5 @@ package com.fasterxml.jackson.dataformat.avro; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.avro.reflect.*; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.Version; @@ -24,6 +18,24 @@ import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.TimestampMillisecondSerializers; +import org.apache.avro.reflect.AvroAlias; +import org.apache.avro.reflect.AvroDefault; +import org.apache.avro.reflect.AvroEncode; +import org.apache.avro.reflect.AvroIgnore; +import org.apache.avro.reflect.AvroName; +import org.apache.avro.reflect.CustomEncoding; +import org.apache.avro.reflect.Nullable; +import org.apache.avro.reflect.Stringable; +import org.apache.avro.reflect.Union; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; /** * Adds support for the following annotations from the Apache Avro implementation: @@ -31,8 +43,8 @@ *

  • {@link AvroIgnore @AvroIgnore} - Alias for JsonIgnore
  • *
  • {@link AvroName @AvroName("custom Name")} - Alias for JsonProperty("custom name")
  • *
  • {@link AvroDefault @AvroDefault("default value")} - Alias for JsonProperty.defaultValue, to - * define default value for generated Schemas - *
  • + * define default value for generated Schemas + * *
  • {@link Nullable @Nullable} - Alias for JsonProperty(required = false)
  • *
  • {@link Stringable @Stringable} - Alias for JsonCreator on the constructor and JsonValue on * the {@link #toString()} method.
  • @@ -41,156 +53,170 @@ * * @since 2.9 */ -public class AvroAnnotationIntrospector extends AnnotationIntrospector -{ - private static final long serialVersionUID = 1L; - - public AvroAnnotationIntrospector() { } - - @Override - public Version version() { - return PackageVersion.VERSION; - } - - @Override - public boolean hasIgnoreMarker(AnnotatedMember m) { - return _findAnnotation(m, AvroIgnore.class) != null; - } - - @Override - public PropertyName findNameForSerialization(Annotated a) { - return _findName(a); - } - - @Override - public PropertyName findNameForDeserialization(Annotated a) { - return _findName(a); - } - - @Override - public Object findDeserializer(Annotated am) { - AvroEncode ann = _findAnnotation(am, AvroEncode.class); - if (ann != null) { - return new CustomEncodingDeserializer<>((CustomEncoding)ClassUtil.createInstance(ann.using(), true)); - } - return null; - } - - @Override - public String findPropertyDefaultValue(Annotated m) { - AvroDefault ann = _findAnnotation(m, AvroDefault.class); - return (ann == null) ? null : ann.value(); - } - - @Override - public List findPropertyAliases(Annotated m) { - AvroAlias ann = _findAnnotation(m, AvroAlias.class); - if (ann == null) { - return null; - } - return Collections.singletonList(PropertyName.construct(ann.alias())); - } - - protected PropertyName _findName(Annotated a) - { - AvroName ann = _findAnnotation(a, AvroName.class); - return (ann == null) ? null : PropertyName.construct(ann.value()); - } - - @Override - public Boolean hasRequiredMarker(AnnotatedMember m) { - if (_hasAnnotation(m, Nullable.class)) { - return Boolean.FALSE; - } - return null; - } - - @Override - public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { - if (a instanceof AnnotatedConstructor) { - AnnotatedConstructor constructor = (AnnotatedConstructor) a; - // 09-Mar-2017, tatu: Ideally would allow mix-ins etc, but for now let's take - // a short-cut here: - Class declClass = constructor.getDeclaringClass(); - if (declClass.getAnnotation(Stringable.class) != null) { - if (constructor.getParameterCount() == 1 - && String.class.equals(constructor.getRawParameterType(0))) { - return JsonCreator.Mode.DELEGATING; - } - } - } - return null; - } - - @Override - public Object findSerializer(Annotated a) { - if (a.hasAnnotation(Stringable.class)) { - return ToStringSerializer.class; - } - AvroEncode ann = _findAnnotation(a, AvroEncode.class); - if (ann != null) { - return new CustomEncodingSerializer<>((CustomEncoding)ClassUtil.createInstance(ann.using(), true)); - } - return null; - } - - @Override - public List findSubtypes(Annotated a) - { - Class[] types = _getUnionTypes(a); - if (types == null) { - return null; - } - ArrayList names = new ArrayList<>(types.length); - for (Class subtype : types) { - names.add(new NamedType(subtype, AvroSchemaHelper.getTypeId(subtype))); +public class AvroAnnotationIntrospector extends AnnotationIntrospector { + private static final long serialVersionUID = 1L; + + public AvroAnnotationIntrospector() { + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + @Override + public boolean hasIgnoreMarker(AnnotatedMember m) { + return _findAnnotation(m, AvroIgnore.class) != null; + } + + @Override + public PropertyName findNameForSerialization(Annotated a) { + return _findName(a); + } + + @Override + public PropertyName findNameForDeserialization(Annotated a) { + return _findName(a); + } + + @Override + public Object findDeserializer(Annotated am) { + AvroEncode ann = _findAnnotation(am, AvroEncode.class); + if (ann != null) { + return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); + } + return null; + } + + @Override + public String findPropertyDefaultValue(Annotated m) { + AvroDefault ann = _findAnnotation(m, AvroDefault.class); + return (ann == null) ? null : ann.value(); + } + + @Override + public List findPropertyAliases(Annotated m) { + AvroAlias ann = _findAnnotation(m, AvroAlias.class); + if (ann == null) { + return null; + } + return Collections.singletonList(PropertyName.construct(ann.alias())); + } + + protected PropertyName _findName(Annotated a) { + AvroName ann = _findAnnotation(a, AvroName.class); + return (ann == null) ? null : PropertyName.construct(ann.value()); + } + + @Override + public Boolean hasRequiredMarker(AnnotatedMember m) { + if (_hasAnnotation(m, Nullable.class)) { + return Boolean.FALSE; + } + return null; + } + + @Override + public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { + if (a instanceof AnnotatedConstructor) { + AnnotatedConstructor constructor = (AnnotatedConstructor) a; + // 09-Mar-2017, tatu: Ideally would allow mix-ins etc, but for now let's take + // a short-cut here: + Class declClass = constructor.getDeclaringClass(); + if (declClass.getAnnotation(Stringable.class) != null) { + if (constructor.getParameterCount() == 1 + && String.class.equals(constructor.getRawParameterType(0))) { + return JsonCreator.Mode.DELEGATING; } - return names; - } - - @Override - public TypeResolverBuilder findTypeResolver(MapperConfig config, AnnotatedClass ac, JavaType baseType) { - return _findTypeResolver(config, ac, baseType); - } - - @Override - public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, AnnotatedMember am, JavaType baseType) { - return _findTypeResolver(config, am, baseType); - } - - @Override - public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig config, AnnotatedMember am, JavaType containerType) { - return _findTypeResolver(config, am, containerType); - } - - protected TypeResolverBuilder _findTypeResolver(MapperConfig config, Annotated ann, JavaType baseType) { - // 14-Apr-2017, tatu: There are two ways to enable polymorphic typing, above and beyond - // basic Jackson: use of `@Union`, and "default typing" approach for `java.lang.Object`: - // latter since Avro support for "untyped" values is otherwise difficult. - // This seems to work for now, but maybe needs more work in future... - if (baseType.isJavaLangObject() || (_getUnionTypes(ann) != null)) { - TypeResolverBuilder resolver = new AvroTypeResolverBuilder(); - JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) { - resolver = resolver.defaultImpl(typeInfo.defaultImpl()); - } - return resolver; - } - return null; - } - - protected Class[] _getUnionTypes(Annotated a) { - Union ann = _findAnnotation(a, Union.class); - if (ann != null) { - // 14-Apr-2017, tatu: I think it makes sense to require non-empty List, as this allows - // disabling annotation with overrides. But one could even consider requiring more than - // one (where single type is not really polymorphism)... for now, however, just one - // is acceptable, and maybe that has valid usages. - Class[] c = ann.value(); - if (c.length > 0) { - return c; - } - } - return null; - } + } + } + return null; + } + + @Override + public Object findSerializer(Annotated a) { + if (a.hasAnnotation(Stringable.class)) { + return ToStringSerializer.class; + } + AvroEncode ann = _findAnnotation(a, AvroEncode.class); + if (ann != null) { + return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (timestampMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return TimestampMillisecondSerializers.DATE; + } + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return TimestampMillisecondSerializers.LOCAL_DATE_TIME; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return TimestampMillisecondSerializers.ZONED_DATE_TIME; + } + if(a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return TimestampMillisecondSerializers.OFFSET_DATE_TIME; + } + } + + return null; + } + + @Override + public List findSubtypes(Annotated a) { + Class[] types = _getUnionTypes(a); + if (types == null) { + return null; + } + ArrayList names = new ArrayList<>(types.length); + for (Class subtype : types) { + names.add(new NamedType(subtype, AvroSchemaHelper.getTypeId(subtype))); + } + return names; + } + + @Override + public TypeResolverBuilder findTypeResolver(MapperConfig config, AnnotatedClass ac, JavaType baseType) { + return _findTypeResolver(config, ac, baseType); + } + + @Override + public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, AnnotatedMember am, JavaType baseType) { + return _findTypeResolver(config, am, baseType); + } + + @Override + public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig config, AnnotatedMember am, JavaType containerType) { + return _findTypeResolver(config, am, containerType); + } + + protected TypeResolverBuilder _findTypeResolver(MapperConfig config, Annotated ann, JavaType baseType) { + // 14-Apr-2017, tatu: There are two ways to enable polymorphic typing, above and beyond + // basic Jackson: use of `@Union`, and "default typing" approach for `java.lang.Object`: + // latter since Avro support for "untyped" values is otherwise difficult. + // This seems to work for now, but maybe needs more work in future... + if (baseType.isJavaLangObject() || (_getUnionTypes(ann) != null)) { + TypeResolverBuilder resolver = new AvroTypeResolverBuilder(); + JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) { + resolver = resolver.defaultImpl(typeInfo.defaultImpl()); + } + return resolver; + } + return null; + } + + protected Class[] _getUnionTypes(Annotated a) { + Union ann = _findAnnotation(a, Union.class); + if (ann != null) { + // 14-Apr-2017, tatu: I think it makes sense to require non-empty List, as this allows + // disabling annotation with overrides. But one could even consider requiring more than + // one (where single type is not really polymorphism)... for now, however, just one + // is acceptable, and maybe that has valid usages. + Class[] c = ann.value(); + if (c.length > 0) { + return c; + } + } + return null; + } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java new file mode 100644 index 000000000..ea41cad9c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java @@ -0,0 +1,39 @@ +package com.fasterxml.jackson.dataformat.avro; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +public class AvroMicroTimeModule extends SimpleModule { + public AvroMicroTimeModule() { + super.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); + + } + + + static class LocalDateTimeSerializer extends StdSerializer { + protected LocalDateTimeSerializer() { + super(LocalDateTime.class); + } + + + + @Override + public void serializeWithType(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { + super.serializeWithType(value, gen, serializers, typeSer); + } + + @Override + public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + localDateTime.toInstant(ZoneOffset.UTC).getNano() * 1000L + ); + } + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java index 0f187e97a..4214d96d0 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; import org.apache.avro.io.BinaryDecoder; @@ -374,22 +376,28 @@ public int decodeEnum() throws IOException { @Override public JsonToken decodeBytesDecimal(int scale) throws IOException { - return null; + decodeBytes(); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; } @Override public void skipBytesDecimal() throws IOException { - + skipBytes(); } @Override public JsonToken decodeFixedDecimal(int scale, int size) throws IOException { - return null; + decodeFixed(size); + _numberBigDecimal = new BigDecimal(new BigInteger(_binaryValue), scale); + _numTypesValid = NR_BIGDECIMAL; + return JsonToken.VALUE_NUMBER_FLOAT; } @Override public void skipFixedDecimal(int size) throws IOException { - + skipFixed(size); } /* diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java new file mode 100644 index 000000000..0747e19ca --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; + +public class TimestampMillisecondSerializers { + public static final JsonSerializer LOCAL_DATE_TIME = new LocalDateTimeSerializer(); + public static final JsonSerializer DATE = new DateSerializer(); + public static final JsonSerializer ZONED_DATE_TIME = new ZonedDateTimeSerializer(); + public static final JsonSerializer OFFSET_DATE_TIME = new OffsetDateTimeSerializer(); + + static class DateSerializer extends JsonSerializer { + @Override + public void serialize(Date d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(d.getTime()); + } + } + + static class LocalDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(LocalDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant(ZoneOffset.UTC).toEpochMilli() + ); + } + } + + static class ZonedDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(ZonedDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant().toEpochMilli() + ); + } + } + + static class OffsetDateTimeSerializer extends JsonSerializer { + @Override + public void serialize(OffsetDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber( + d.toInstant().toEpochMilli() + ); + } + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java index 3736b8d1b..c7283592e 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java @@ -98,13 +98,13 @@ public void testSimplest() throws Exception public void testBinaryOk() throws Exception { ObjectMapper mapper = new ObjectMapper(new AvroFactory()); - Binary bin = new Binary("Foo", new byte[] { 1, 2, 3, 4 }); + Binary bin = new Binary("LocalDateTimeSerializer", new byte[] { 1, 2, 3, 4 }); byte[] bytes = mapper.writer(SCHEMA_WITH_BINARY_JSON).writeValueAsBytes(bin); assertEquals(9, bytes.length); assertNotNull(bytes); Binary output = mapper.reader(SCHEMA_WITH_BINARY_JSON).forType(Binary.class).readValue(bytes); assertNotNull(output); - assertEquals("Foo", output.name); + assertEquals("LocalDateTimeSerializer", output.name); assertNotNull(output.value); Assert.assertArrayEquals(bin.value, output.value); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java index b48edc30f..6212b0131 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java @@ -43,7 +43,7 @@ static class BytesDecimal extends TestData { public BigDecimal value; @Override - BigDecimal value() { + public BigDecimal value() { return this.value; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java index 11343f8b1..a6582bcf2 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java @@ -43,7 +43,7 @@ static class FixedDecimal extends TestData { public BigDecimal value; @Override - BigDecimal value() { + public BigDecimal value() { return this.value; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java index 4f2bc3563..724abd140 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import junit.framework.TestCase; import org.apache.avro.Schema; @@ -48,6 +49,11 @@ protected void setUp() throws Exception { this.logicalType = logicalType(); System.out.println(recordSchema.toString(true)); + configure(this.mapper); + } + + protected void configure(AvroMapper mapper) { + } public void testSchemaType() { diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java index 66aa7b2b1..5ba0e240f 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java @@ -1,5 +1,5 @@ package com.fasterxml.jackson.dataformat.avro.logicaltypes; -abstract class TestData { - abstract T value(); +public abstract class TestData { + public abstract T value(); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java index ea14c161c..efee3e6f4 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java @@ -26,6 +26,8 @@ public Date value() { } } + static final Date VALUE = new Date(1526943920123L); + @Override protected Class dataClass() { return RequiredTimestampMicros.class; @@ -44,12 +46,12 @@ protected String logicalType() { @Override protected RequiredTimestampMicros testData() { RequiredTimestampMicros v = new RequiredTimestampMicros(); - v.value = new Date(1526943920123L); + v.value = VALUE; return v; } @Override protected Object convertedValue() { - return 1526943920123L * 1000L; + return VALUE.getTime() * 1000L; } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java deleted file mode 100644 index 0f917fb9e..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTestOld.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import junit.framework.TestCase; - -import java.io.IOException; -import java.util.Date; - -public class TimestampMillisTestOld extends TestCase { - - static class RequiredTimestampMillis { - @JsonProperty(required = true) - @AvroTimestampMillisecond - public Date value; - } - - - public void testRoundtrip() throws IOException { - final AvroMapper mapper = new AvroMapper(); - final AvroSchema avroSchema = mapper.schemaFor(RequiredTimestampMillis.class); - System.out.println(avroSchema.getAvroSchema().toString(true)); - final RequiredTimestampMillis expected = new RequiredTimestampMillis(); - expected.value = new Date(1526943920123L); - byte[] buffer = mapper.writer(avroSchema) - .writeValueAsBytes(expected); - final RequiredTimestampMillis actual = mapper.reader(avroSchema).forType(RequiredTimestampMillis.class) - .readValue(buffer); - assertNotNull(actual); - assertEquals(expected.value, actual.value); - } - -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java new file mode 100644 index 000000000..498f98a18 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +public class TimeMillisDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final Date VALUE = new Date(1526955327123L); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java new file mode 100644 index 000000000..23660f2ae --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java @@ -0,0 +1,65 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimeMillisLocalDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java new file mode 100644 index 000000000..70fe24382 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java @@ -0,0 +1,66 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TimeMillisOffsetDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + ZonedDateTime value; + + @Override + public ZonedDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java new file mode 100644 index 000000000..460f0a3d4 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java @@ -0,0 +1,65 @@ +package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimeMillisZonedDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} From 59fd5c50f06ace726b73e93960d42c03c0d08eb0 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 13:18:01 -0500 Subject: [PATCH 18/25] Refactored java8 classes out to it's own module. This will allow the avro module to stay java 7 while supporting mapping of java.time out to their own serializers. --- avro-java8/pom.xml | 96 ++++++++++++ .../AvroJavaTimeAnnotationIntrospector.java | 140 ++++++++++++++++++ .../avro/java8/AvroJavaTimeModule.java | 20 +++ .../avro/java8/PackageVersion.java.in | 20 +++ .../java8/deser/BaseTimeJsonDeserializer.java | 29 ++++ .../java8/deser/LocalDateDeserializer.java | 18 +++ .../deser/LocalDateTimeDeserializer.java | 21 +++ .../java8/deser/LocalTimeDeserializer.java | 28 ++++ .../deser/OffsetDateTimeDeserializer.java | 21 +++ .../deser/ZonedDateTimeDeserializer.java | 21 +++ .../java8/ser/BaseTimeJsonSerializer.java | 41 +++++ .../avro/java8/ser/LocalDateSerializer.java | 17 +++ .../java8/ser/LocalDateTimeSerializer.java | 22 +++ .../avro/java8/ser/LocalTimeSerializer.java | 38 +++++ .../java8/ser/OffsetDateTimeSerializer.java | 21 +++ .../java8/ser/ZonedDateTimeSerializer.java | 21 +++ .../java8}/logicaltypes/BytesDecimalTest.java | 2 +- .../java8}/logicaltypes/FixedDecimalTest.java | 2 +- .../logicaltypes/LogicalTypeTestCase.java | 16 +- .../avro/java8}/logicaltypes/TestData.java | 2 +- .../logicaltypes/TimestampMicrosTest.java | 9 +- .../avro/java8/logicaltypes/UUIDTest.java | 52 +++++++ .../logicaltypes/time/DateLocalDateTest.java | 60 ++++++++ .../time/TimeMicrosLocalTimeTest.java | 60 ++++++++ .../time/TimeMillisLocalTimeTest.java | 59 ++++++++ .../time/TimestampMicrosDateTest.java | 52 +++++++ .../TimestampMicrosLocalDateTimeTest.java | 67 +++++++++ .../TimestampMicrosOffsetDateTimeTest.java | 58 ++++++++ .../TimestampMicrosZonedDateTimeTest.java | 20 +-- .../time/TimestampMillisDateTest.java | 13 +- .../TimestampMillisLocalDateTimeTest.java | 16 +- .../TimestampMillisOffsetDateTimeTest.java | 21 ++- .../TimestampMillisZonedDateTimeTest.java | 57 +++++++ .../avro/AvroAnnotationIntrospector.java | 54 +++++-- .../dataformat/avro/AvroMicroTimeModule.java | 39 ----- .../jackson/dataformat/avro/AvroUUID.java | 19 +++ .../AvroDateTimestampMicrosDeserializer.java | 24 +++ .../AvroDateTimestampMillisDeserializer.java | 19 +++ .../avro/deser/AvroReaderFactory.java | 34 ++--- .../avro/deser/AvroUUIDDeserializer.java | 18 +++ .../dataformat/avro/schema/RecordVisitor.java | 44 +++--- .../AvroDateTimestampMicrosSerializer.java | 17 +++ .../AvroDateTimestampMillisSerializer.java | 17 +++ .../avro/ser/AvroUUIDSerializer.java | 17 +++ .../ser/TimestampMillisecondSerializers.java | 53 ------- .../dataformat/avro/SimpleGenerationTest.java | 4 +- .../logicaltypes/TimestampMillisTest.java | 49 ------ .../avro/schema/TestLogicalTypes.java | 16 ++ pom.xml | 1 + 49 files changed, 1313 insertions(+), 252 deletions(-) create mode 100644 avro-java8/pom.xml create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/BytesDecimalTest.java (94%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/FixedDecimalTest.java (95%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/LogicalTypeTestCase.java (90%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/TestData.java (50%) rename {avro/src/test/java/com/fasterxml/jackson/dataformat/avro => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8}/logicaltypes/TimestampMicrosTest.java (74%) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java (63%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java (66%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java (65%) rename avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java => avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java (59%) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java delete mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml new file mode 100644 index 000000000..ee763eeb2 --- /dev/null +++ b/avro-java8/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + com.fasterxml.jackson.dataformat + jackson-dataformats-binary + 2.9.6-SNAPSHOT + + jackson-dataformat-avro-java8 + Jackson dataformat: Avro Java 8 + bundle + Support for reading and writing AVRO-encoded data via Jackson +abstractions. + + http://github.com/FasterXML/jackson-dataformats-binary + + + + com/fasterxml/jackson/dataformat/avro/java8 + ${project.groupId}.avro.java8 + + + + + + com.fasterxml.jackson.core + jackson-annotations + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-avro + ${project.version} + + + org.apache.avro + avro + 1.8.1 + + + + + ch.qos.logback + logback-classic + 1.1.3 + test + + + + org.projectlombok + lombok + 1.16.14 + test + + + + + org.assertj + assertj-core + 2.5.0 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + + + com.google.code.maven-replacer-plugin + replacer + + + process-packageVersion + generate-sources + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java new file mode 100644 index 000000000..745b67a70 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -0,0 +1,140 @@ +package com.fasterxml.jackson.dataformat.avro.java8; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.AnnotationIntrospector; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.OffsetDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.deser.ZonedDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.OffsetDateTimeSerializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.ZonedDateTimeSerializer; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +class AvroJavaTimeAnnotationIntrospector extends AnnotationIntrospector { + static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector(); + + @Override + public Object findSerializer(Annotated a) { + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (null != timestampMillisecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MILLIS; + } + } + + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (null != timestampMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MICROS; + } + } + + AvroDate date = _findAnnotation(a, AvroDate.class); + if (null != date) { + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateSerializer.INSTANCE; + } + } + + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (null != timeMillisecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MILLIS; + } + } + + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (null != timeMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MICROS; + } + } + + return super.findSerializer(a); + + } + + @Override + public Object findDeserializer(Annotated a) { + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (null != timestampMillisecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MILLIS; + } + } + + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (null != timestampMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MICROS; + } + } + + AvroDate date = _findAnnotation(a, AvroDate.class); + if (null != date) { + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateDeserializer.INSTANCE; + } + } + + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (null != timeMillisecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MILLIS; + } + } + + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (null != timeMicrosecond) { + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MICROS; + } + } + + return super.findDeserializer(a); + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java new file mode 100644 index 000000000..977a21286 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeModule.java @@ -0,0 +1,20 @@ +package com.fasterxml.jackson.dataformat.avro.java8; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.dataformat.avro.AvroModule; + +public class AvroJavaTimeModule extends AvroModule { + + + public AvroJavaTimeModule() { + withAnnotationIntrospector(AvroJavaTimeAnnotationIntrospector.INSTANCE); + } + + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in new file mode 100644 index 000000000..7860aa14b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/PackageVersion.java.in @@ -0,0 +1,20 @@ +package @package@; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.Versioned; +import com.fasterxml.jackson.core.util.VersionUtil; + +/** + * Automatically generated from PackageVersion.java.in during + * packageVersion-generate execution of maven-replacer-plugin in + * pom.xml. + */ +public final class PackageVersion implements Versioned { + public final static Version VERSION = VersionUtil.parseVersion( + "@projectversion@", "@projectgroupid@", "@projectartifactid@"); + + @Override + public Version version() { + return VERSION; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java new file mode 100644 index 000000000..2e690b36c --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { + final TimeUnit resolution; + final ZoneId zoneId = ZoneId.of("UTC"); + + BaseTimeJsonDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + abstract T fromInstant(Instant input); + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final long input = p.getLongValue(); + final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + final Instant instant = Instant.ofEpochMilli(output); + return fromInstant(instant); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java new file mode 100644 index 000000000..41b321b25 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateDeserializer.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalDate; + +public class LocalDateDeserializer extends JsonDeserializer { + public static final JsonDeserializer INSTANCE = new LocalDateDeserializer(); + + @Override + public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + return LocalDate.ofEpochDay(jsonParser.getLongValue()); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java new file mode 100644 index 000000000..b63790dc9 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +public class LocalDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new LocalDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new LocalDateTimeDeserializer(TimeUnit.MICROSECONDS); + + LocalDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected LocalDateTime fromInstant(Instant input) { + return LocalDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java new file mode 100644 index 000000000..0d2a24f2b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/LocalTimeDeserializer.java @@ -0,0 +1,28 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class LocalTimeDeserializer extends JsonDeserializer { + public static JsonDeserializer MILLIS = new LocalTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new LocalTimeDeserializer(TimeUnit.MICROSECONDS); + + final TimeUnit resolution; + + LocalTimeDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public LocalTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + long nanos = this.resolution.toNanos(value); + return LocalTime.ofNanoOfDay(nanos); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java new file mode 100644 index 000000000..80b472b0c --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.concurrent.TimeUnit; + +public class OffsetDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new OffsetDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new OffsetDateTimeDeserializer(TimeUnit.MICROSECONDS); + + OffsetDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected OffsetDateTime fromInstant(Instant input) { + return OffsetDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java new file mode 100644 index 000000000..c1ae6eae6 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +public class ZonedDateTimeDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new ZonedDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new ZonedDateTimeDeserializer(TimeUnit.MICROSECONDS); + + ZonedDateTimeDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + protected ZonedDateTime fromInstant(Instant input) { + return ZonedDateTime.ofInstant(input, this.zoneId); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java new file mode 100644 index 000000000..04ab716ba --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java @@ -0,0 +1,41 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +public abstract class BaseTimeJsonSerializer extends JsonSerializer { + final TimeUnit resolution; + final ZoneId zoneId = ZoneId.of("UTC"); + + BaseTimeJsonSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + abstract Instant toInstant(T input); + + @Override + public void serialize(T input, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final Instant instant = toInstant(input); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + + TimeUnit.NANOSECONDS.toMicros(instant.getNano()); + break; + case MILLISECONDS: + output = instant.toEpochMilli(); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + jsonGenerator.writeNumber(output); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java new file mode 100644 index 000000000..7a37aedfd --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDate; + +public class LocalDateSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new LocalDateSerializer(); + + @Override + public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(localDate.toEpochDay()); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java new file mode 100644 index 000000000..f40c3d6df --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalDateTimeSerializer.java @@ -0,0 +1,22 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; + +public class LocalDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new LocalDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new LocalDateTimeSerializer(TimeUnit.MICROSECONDS); + + LocalDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(LocalDateTime input) { + return input.toInstant(ZoneOffset.UTC); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java new file mode 100644 index 000000000..eb6268765 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/LocalTimeSerializer.java @@ -0,0 +1,38 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class LocalTimeSerializer extends JsonSerializer { + public static final JsonSerializer MILLIS = new LocalTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new LocalTimeSerializer(TimeUnit.MICROSECONDS); + + private final TimeUnit resolution; + + LocalTimeSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public void serialize(LocalTime localTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + switch (this.resolution) { + case MICROSECONDS: + long micros = TimeUnit.NANOSECONDS.toMicros(localTime.toNanoOfDay()); + jsonGenerator.writeNumber(micros); + break; + case MILLISECONDS: + int millis = (int)TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay()); + jsonGenerator.writeNumber(millis); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java new file mode 100644 index 000000000..65f14147b --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.concurrent.TimeUnit; + +public class OffsetDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new OffsetDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new OffsetDateTimeSerializer(TimeUnit.MICROSECONDS); + + OffsetDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(OffsetDateTime input) { + return input.toInstant(); + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java new file mode 100644 index 000000000..7cc61b697 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +public class ZonedDateTimeSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new ZonedDateTimeSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new ZonedDateTimeSerializer(TimeUnit.MICROSECONDS); + + ZonedDateTimeSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(ZonedDateTime input) { + return input.toInstant(); + } +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java similarity index 94% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java index 6212b0131..6b3fffce0 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/BytesDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDecimal; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java similarity index 95% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java index a6582bcf2..410b9b606 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/FixedDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDecimal; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java similarity index 90% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java index 724abd140..e6475a6bf 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/LogicalTypeTestCase.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java @@ -1,9 +1,9 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import junit.framework.TestCase; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; @@ -26,15 +26,19 @@ public abstract class LogicalTypeTestCase extends TestCase { protected String logicalType; protected abstract Class dataClass(); + protected abstract Schema.Type schemaType(); + protected abstract String logicalType(); + protected abstract T testData(); + protected abstract Object convertedValue(); @Override protected void setUp() throws Exception { - this.mapper = new AvroMapper(); + this.mapper = new AvroMapper(new AvroJavaTimeModule()); this.dataClass = dataClass(); this.avroSchema = mapper.schemaFor(this.dataClass); @@ -61,8 +65,8 @@ public void testSchemaType() { } public void testLogicalType() { - assertNotNull("schema.getLogicalType() should not return null",this.schema.getLogicalType()); - assertEquals("schema.getLogicalType().getName() does not match.",this.logicalType, this.schema.getLogicalType().getName()); + assertNotNull("schema.getLogicalType() should not return null", this.schema.getLogicalType()); + assertEquals("schema.getLogicalType().getName() does not match.", this.logicalType, this.schema.getLogicalType().getName()); } byte[] serialize(T expected) throws JsonProcessingException { @@ -83,7 +87,7 @@ public void testAvroSerialization() throws IOException { final byte[] actualbytes = serialize(expected); final Object convertedValue = convertedValue(); byte[] expectedBytes; - try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(outputStream, null); GenericData.Record record = new GenericData.Record(this.recordSchema); record.put("value", convertedValue); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java similarity index 50% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java index 5ba0e240f..c01309015 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TestData.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; public abstract class TestData { public abstract T value(); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java similarity index 74% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java index efee3e6f4..8fc368658 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMicrosTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java @@ -1,16 +1,9 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroSchema; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import junit.framework.TestCase; -import org.apache.avro.Conversions; import org.apache.avro.Schema; -import org.apache.avro.data.TimeConversions; -import java.io.IOException; import java.util.Date; public class TimestampMicrosTest extends LogicalTypeTestCase { diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java new file mode 100644 index 000000000..69c875d8f --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import org.apache.avro.Conversions; +import org.apache.avro.Schema; + +import java.math.BigDecimal; +import java.util.UUID; + +public class UUIDTest extends LogicalTypeTestCase { + static final UUID VALUE = UUID.randomUUID(); + + @Override + protected Class dataClass() { + return UUIDTestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.STRING; + } + + @Override + protected String logicalType() { + return "uuid"; + } + + @Override + protected UUIDTestCase testData() { + UUIDTestCase v = new UUIDTestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toString(); + } + + static class UUIDTestCase extends TestData { + @JsonProperty(required = true) + @AvroUUID + public UUID value; + + @Override + public UUID value() { + return this.value; + } + } +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java new file mode 100644 index 000000000..fe5673aa1 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.util.Date; + +public class DateLocalDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "date"; + } + + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toEpochDay(); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroDate + LocalDate value; + + @Override + public LocalDate value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java new file mode 100644 index 000000000..8c010ef1e --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class TimeMicrosLocalTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "time-micros"; + } + + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return TimeUnit.NANOSECONDS.toMicros(VALUE.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMicrosecond + LocalTime value; + + @Override + public LocalTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java new file mode 100644 index 000000000..77ca03b6e --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -0,0 +1,59 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class TimeMillisLocalTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return TimeUnit.NANOSECONDS.toMillis(VALUE.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + LocalTime value; + + @Override + public LocalTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java new file mode 100644 index 000000000..79a0cb266 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.util.Date; + +public class TimestampMicrosDateTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final Date VALUE = new Date(1526955327123L); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L * 1000L; + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java new file mode 100644 index 000000000..0fc78f652 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -0,0 +1,67 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; + +public class TimestampMicrosLocalDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + Instant instant = VALUE.toInstant(ZoneOffset.UTC); + return (TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + TimeUnit.NANOSECONDS.toMicros(instant.getNano())); + } + + @Override + protected void configure(AvroMapper mapper) { + mapper.registerModule(new AvroJavaTimeModule()); + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + LocalDateTime value; + + @Override + public LocalDateTime value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java new file mode 100644 index 000000000..bafaea035 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-micros"; + } + + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L * 1000L; + } + + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMicrosecond + OffsetDateTime value; + + @Override + public OffsetDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java similarity index 63% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index 70fe24382..c39af3fb8 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -1,12 +1,12 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; @@ -14,7 +14,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; -public class TimeMillisOffsetDateTimeTest extends LogicalTypeTestCase { +public class TimestampMicrosZonedDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -27,7 +27,7 @@ protected Schema.Type schemaType() { @Override protected String logicalType() { - return "timestamp-millis"; + return "timestamp-micros"; } static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( @@ -44,17 +44,17 @@ protected TestCase testData() { @Override protected Object convertedValue() { - return 1526955327123L; + return 1526955327123L * 1000L; } @Override protected void configure(AvroMapper mapper) { - + mapper.registerModule(new AvroJavaTimeModule()); } static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroTimestampMicrosecond ZonedDateTime value; @Override diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java similarity index 66% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index 498f98a18..65f9d6444 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -1,20 +1,15 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.util.Date; -public class TimeMillisDateTest extends LogicalTypeTestCase { +public class TimestampMillisDateTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java similarity index 65% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index 23660f2ae..8e7526d91 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -1,19 +1,16 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -public class TimeMillisLocalDateTimeTest extends LogicalTypeTestCase { +public class TimestampMillisLocalDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -46,11 +43,6 @@ protected Object convertedValue() { return 1526955327123L; } - @Override - protected void configure(AvroMapper mapper) { - - } - static class TestCase extends TestData { @JsonProperty(required = true) @AvroTimestampMillisecond diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java similarity index 59% rename from avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index 460f0a3d4..b68002156 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/time/TimeMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -1,19 +1,18 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes.time; +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroMicroTimeModule; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.logicaltypes.TestData; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; -public class TimeMillisZonedDateTimeTest extends LogicalTypeTestCase { +public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase { @Override protected Class dataClass() { return TestCase.class; @@ -29,7 +28,7 @@ protected String logicalType() { return "timestamp-millis"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( Instant.ofEpochMilli(1526955327123L), ZoneId.of("UTC") ); @@ -51,13 +50,13 @@ protected void configure(AvroMapper mapper) { } - static class TestCase extends TestData { + static class TestCase extends TestData { @JsonProperty(required = true) @AvroTimestampMillisecond - LocalDateTime value; + OffsetDateTime value; @Override - public LocalDateTime value() { + public OffsetDateTime value() { return this.value; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java new file mode 100644 index 000000000..aa7e13e66 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -0,0 +1,57 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TimestampMillisZonedDateTimeTest extends LogicalTypeTestCase { + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.LONG; + } + + @Override + protected String logicalType() { + return "timestamp-millis"; + } + + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + return 1526955327123L; + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimestampMillisecond + ZonedDateTime value; + + @Override + public ZonedDateTime value() { + return this.value; + } + } + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index fa596af97..e6e5aed10 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,9 +16,14 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMicrosDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMillisDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMicrosSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMillisSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; -import com.fasterxml.jackson.dataformat.avro.ser.TimestampMillisecondSerializers; import org.apache.avro.reflect.AvroAlias; import org.apache.avro.reflect.AvroDefault; import org.apache.avro.reflect.AvroEncode; @@ -29,13 +34,11 @@ import org.apache.avro.reflect.Stringable; import org.apache.avro.reflect.Union; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.UUID; /** * Adds support for the following annotations from the Apache Avro implementation: @@ -80,11 +83,31 @@ public PropertyName findNameForDeserialization(Annotated a) { } @Override - public Object findDeserializer(Annotated am) { - AvroEncode ann = _findAnnotation(am, AvroEncode.class); + public Object findDeserializer(Annotated a) { + AvroEncode ann = _findAnnotation(a, AvroEncode.class); if (ann != null) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); + if (timestampMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMillisDeserializer.INSTANCE; + } + } + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (timestampMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMicrosDeserializer.INSTANCE; + } + } + AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); + if (avroUUID != null) { + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDDeserializer.INSTANCE; + } + } + return null; } @@ -145,16 +168,19 @@ public Object findSerializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return TimestampMillisecondSerializers.DATE; + return AvroDateTimestampMillisSerializer.INSTANCE; } - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return TimestampMillisecondSerializers.LOCAL_DATE_TIME; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return TimestampMillisecondSerializers.ZONED_DATE_TIME; + } + AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); + if (timestampMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampMicrosSerializer.INSTANCE; } - if(a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return TimestampMillisecondSerializers.OFFSET_DATE_TIME; + } + AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); + if (avroUUID != null) { + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDSerializer.INSTANCE; } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java deleted file mode 100644 index ea41cad9c..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMicroTimeModule.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -public class AvroMicroTimeModule extends SimpleModule { - public AvroMicroTimeModule() { - super.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); - - } - - - static class LocalDateTimeSerializer extends StdSerializer { - protected LocalDateTimeSerializer() { - super(LocalDateTime.class); - } - - - - @Override - public void serializeWithType(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { - super.serializeWithType(value, gen, serializers, typeSer); - } - - @Override - public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - localDateTime.toInstant(ZoneOffset.UTC).getNano() * 1000L - ); - } - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java new file mode 100644 index 000000000..27acd79f5 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java @@ -0,0 +1,19 @@ +package com.fasterxml.jackson.dataformat.avro; + +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only used during Avro schema generation; has no effect on data (de)serialization. + *

    + * Instructs the {@link AvroSchemaGenerator AvroSchemaGenerator} + * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroUUID { +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java new file mode 100644 index 000000000..bde804621 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java @@ -0,0 +1,24 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMicrosDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroDateTimestampMicrosDeserializer(); + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + + if (value == 0L) { + return new Date(value); + } + + return new Date(value / 1000L); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java new file mode 100644 index 000000000..53039802a --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java @@ -0,0 +1,19 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMillisDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroDateTimestampMillisDeserializer(); + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + long value = jsonParser.getLongValue(); + return new Date(value); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java index 50b21176c..8a9d7d30c 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java @@ -2,27 +2,25 @@ import java.util.*; +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.util.internal.JacksonUtils; -import com.fasterxml.jackson.dataformat.avro.deser.ScalarDecoder.*; -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; - /** * Helper class used for constructing a hierarchic reader for given * (reader-) schema. */ public abstract class AvroReaderFactory { - protected final static ScalarDecoder READER_BOOLEAN = new BooleanDecoder(); - protected final static ScalarDecoder READER_BYTES = new BytesDecoder(); - protected final static ScalarDecoder READER_DOUBLE = new DoubleReader(); - protected final static ScalarDecoder READER_FLOAT = new FloatReader(); - protected final static ScalarDecoder READER_INT = new IntReader(); - protected final static ScalarDecoder READER_LONG = new LongReader(); - protected final static ScalarDecoder READER_NULL = new NullReader(); - protected final static ScalarDecoder READER_STRING = new StringReader(); + protected final static ScalarDecoder READER_BOOLEAN = new ScalarDecoder.BooleanDecoder(); + protected final static ScalarDecoder READER_BYTES = new ScalarDecoder.BytesDecoder(); + protected final static ScalarDecoder READER_DOUBLE = new ScalarDecoder.DoubleReader(); + protected final static ScalarDecoder READER_FLOAT = new ScalarDecoder.FloatReader(); + protected final static ScalarDecoder READER_INT = new ScalarDecoder.IntReader(); + protected final static ScalarDecoder READER_LONG = new ScalarDecoder.LongReader(); + protected final static ScalarDecoder READER_NULL = new ScalarDecoder.NullReader(); + protected final static ScalarDecoder READER_STRING = new ScalarDecoder.StringReader(); /** * To resolve cyclic types, need to keep track of resolved named @@ -60,7 +58,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) case BYTES: if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); - return new BytesDecimalReader( + return new ScalarDecoder.BytesDecimalReader( decimal.getScale() ); } @@ -68,21 +66,21 @@ public ScalarDecoder createScalarValueDecoder(Schema type) case DOUBLE: return READER_DOUBLE; case ENUM: - return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); + return new ScalarDecoder.EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols()); case FIXED: if(type.getLogicalType() != null && "decimal".equals(type.getLogicalType().getName())) { LogicalTypes.Decimal decimal = (LogicalTypes.Decimal) type.getLogicalType(); - return new FixedDecimalReader( + return new ScalarDecoder.FixedDecimalReader( decimal.getScale(), type.getFixedSize() ); } - return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); + return new ScalarDecoder.FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type)); case FLOAT: return READER_FLOAT; case INT: if (AvroSchemaHelper.getTypeId(type) != null) { - return new IntReader(AvroSchemaHelper.getTypeId(type)); + return new ScalarDecoder.IntReader(AvroSchemaHelper.getTypeId(type)); } return READER_INT; case LONG: @@ -91,7 +89,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) return READER_NULL; case STRING: if (AvroSchemaHelper.getTypeId(type) != null) { - return new StringReader(AvroSchemaHelper.getTypeId(type)); + return new ScalarDecoder.StringReader(AvroSchemaHelper.getTypeId(type)); } return READER_STRING; case UNION: @@ -110,7 +108,7 @@ public ScalarDecoder createScalarValueDecoder(Schema type) } readers[i++] = reader; } - return new ScalarUnionDecoder(readers); + return new ScalarDecoder.ScalarUnionDecoder(readers); } case ARRAY: // ok to call just can't handle case MAP: diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java new file mode 100644 index 000000000..fd00c14c8 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroUUIDDeserializer.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.UUID; + +public class AvroUUIDDeserializer extends JsonDeserializer { + public static JsonDeserializer INSTANCE = new AvroUUIDDeserializer(); + @Override + public UUID deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + String value = jsonParser.getText(); + return UUID.fromString(value); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index eb3e9b102..9201099e9 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; @@ -206,27 +207,34 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) writerSchema = LogicalTypes.timeMicros() .addToSchema(Schema.create(Type.LONG)); } else { - JsonSerializer ser = null; + AvroUUID avroUUID = prop.getAnnotation(AvroUUID.class); - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + if(avroUUID != null) { + writerSchema = LogicalTypes.uuid() + .addToSchema(Schema.create(Type.STRING)); + } else { + JsonSerializer ser = null; + + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); + } + ser = prov.findValueSerializer(prop.getType(), prop); } - ser = prov.findValueSerializer(prop.getType(), prop); + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); } } } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java new file mode 100644 index 000000000..d9a0c2cbc --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMicrosSerializer extends JsonSerializer { + public static JsonSerializer INSTANCE = new AvroDateTimestampMicrosSerializer(); + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(date.getTime() * 1000L); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java new file mode 100644 index 000000000..0f3911612 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class AvroDateTimestampMillisSerializer extends JsonSerializer { + public static JsonSerializer INSTANCE = new AvroDateTimestampMillisSerializer(); + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(date.getTime()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java new file mode 100644 index 000000000..c2237044e --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroUUIDSerializer.java @@ -0,0 +1,17 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.UUID; + +public class AvroUUIDSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new AvroUUIDSerializer(); + + @Override + public void serialize(UUID uuid, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(uuid.toString()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java deleted file mode 100644 index 0747e19ca..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/TimestampMillisecondSerializers.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Date; - -public class TimestampMillisecondSerializers { - public static final JsonSerializer LOCAL_DATE_TIME = new LocalDateTimeSerializer(); - public static final JsonSerializer DATE = new DateSerializer(); - public static final JsonSerializer ZONED_DATE_TIME = new ZonedDateTimeSerializer(); - public static final JsonSerializer OFFSET_DATE_TIME = new OffsetDateTimeSerializer(); - - static class DateSerializer extends JsonSerializer { - @Override - public void serialize(Date d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(d.getTime()); - } - } - - static class LocalDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(LocalDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant(ZoneOffset.UTC).toEpochMilli() - ); - } - } - - static class ZonedDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(ZonedDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant().toEpochMilli() - ); - } - } - - static class OffsetDateTimeSerializer extends JsonSerializer { - @Override - public void serialize(OffsetDateTime d, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber( - d.toInstant().toEpochMilli() - ); - } - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java index c7283592e..3736b8d1b 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/SimpleGenerationTest.java @@ -98,13 +98,13 @@ public void testSimplest() throws Exception public void testBinaryOk() throws Exception { ObjectMapper mapper = new ObjectMapper(new AvroFactory()); - Binary bin = new Binary("LocalDateTimeSerializer", new byte[] { 1, 2, 3, 4 }); + Binary bin = new Binary("Foo", new byte[] { 1, 2, 3, 4 }); byte[] bytes = mapper.writer(SCHEMA_WITH_BINARY_JSON).writeValueAsBytes(bin); assertEquals(9, bytes.length); assertNotNull(bytes); Binary output = mapper.reader(SCHEMA_WITH_BINARY_JSON).forType(Binary.class).readValue(bytes); assertNotNull(output); - assertEquals("LocalDateTimeSerializer", output.name); + assertEquals("Foo", output.name); assertNotNull(output.value); Assert.assertArrayEquals(bin.value, output.value); } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java deleted file mode 100644 index 3f3965585..000000000 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/logicaltypes/TimestampMillisTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import org.apache.avro.Schema; - -import java.util.Date; - -public class TimestampMillisTest extends LogicalTypeTestCase { - - static class RequiredTimestampMillis extends TestData { - @JsonProperty(required = true) - @AvroTimestampMillisecond - Date value; - - @Override - public Date value() { - return this.value; - } - } - - @Override - protected Class dataClass() { - return RequiredTimestampMillis.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-millis"; - } - - @Override - protected RequiredTimestampMillis testData() { - RequiredTimestampMillis v = new RequiredTimestampMillis(); - v.value = new Date(1526943920123L); - return v; - } - - @Override - protected Object convertedValue() { - return 1526943920123L; - } -} diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 14ebd663d..3de11d390 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -11,12 +11,15 @@ import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.junit.Assert; import java.math.BigDecimal; import java.util.Date; +import java.util.UUID; public class TestLogicalTypes extends AvroTestBase { @@ -68,6 +71,12 @@ static class DateType { public Date value; } + static class UUIDType { + @AvroUUID + @JsonProperty(required = true) + public UUID value; + } + AvroSchema getSchema(Class cls) throws JsonMappingException { AvroMapper avroMapper = new AvroMapper(); AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); @@ -152,4 +161,11 @@ public void testDateType() throws JsonMappingException { Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.INT, "date"); } + + public void testUUIDType() throws JsonMappingException { + AvroSchema avroSchema = getSchema(UUIDType.class); + Schema schema = avroSchema.getAvroSchema(); + Schema.Field field = schema.getField("value"); + assertLogicalType(field, Schema.Type.STRING, "uuid"); + } } diff --git a/pom.xml b/pom.xml index 819a5f7c1..93105f013 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ cbor smile avro + avro-java8 protobuf ion From 64c3497afc07e55cae4e8255f7ca46898fbc193c Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 13:55:12 -0500 Subject: [PATCH 19/25] Cleaned up logic for converting dates. It's going to be lossy but that is how the official Avro project does it. --- .../java8/deser/BaseTimeJsonDeserializer.java | 14 +++++- .../java8/ser/BaseTimeJsonSerializer.java | 3 +- .../logicaltypes/TimestampMicrosTest.java | 50 ------------------- .../time/TimestampMicrosDateTest.java | 3 +- .../avro/AvroAnnotationIntrospector.java | 14 +++--- .../deser/AvroDateTimestampDeserializer.java | 40 +++++++++++++++ .../AvroDateTimestampMicrosDeserializer.java | 24 --------- .../AvroDateTimestampMillisDeserializer.java | 19 ------- .../AvroDateTimestampMicrosSerializer.java | 17 ------- .../AvroDateTimestampMillisSerializer.java | 17 ------- .../avro/ser/AvroDateTimestampSerializer.java | 39 +++++++++++++++ 11 files changed, 101 insertions(+), 139 deletions(-) delete mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java index 2e690b36c..f32d22316 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java @@ -22,7 +22,19 @@ public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { @Override public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { final long input = p.getLongValue(); - final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MICROSECONDS.toMillis(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } final Instant instant = Instant.ofEpochMilli(output); return fromInstant(instant); } diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java index 04ab716ba..bd660531c 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java @@ -25,8 +25,7 @@ public void serialize(T input, JsonGenerator jsonGenerator, SerializerProvider s final long output; switch (this.resolution) { case MICROSECONDS: - output = TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + - TimeUnit.NANOSECONDS.toMicros(instant.getNano()); + output = TimeUnit.MILLISECONDS.toMicros(instant.toEpochMilli()); break; case MILLISECONDS: output = instant.toEpochMilli(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java deleted file mode 100644 index 8fc368658..000000000 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TimestampMicrosTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import org.apache.avro.Schema; - -import java.util.Date; - -public class TimestampMicrosTest extends LogicalTypeTestCase { - - static class RequiredTimestampMicros extends TestData { - @JsonProperty(required = true) - @AvroTimestampMicrosecond - Date value; - - @Override - public Date value() { - return this.value; - } - } - - static final Date VALUE = new Date(1526943920123L); - - @Override - protected Class dataClass() { - return RequiredTimestampMicros.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-micros"; - } - - @Override - protected RequiredTimestampMicros testData() { - RequiredTimestampMicros v = new RequiredTimestampMicros(); - v.value = VALUE; - return v; - } - - @Override - protected Object convertedValue() { - return VALUE.getTime() * 1000L; - } -} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index 79a0cb266..93449ef82 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -7,6 +7,7 @@ import org.apache.avro.Schema; import java.util.Date; +import java.util.concurrent.TimeUnit; public class TimestampMicrosDateTest extends LogicalTypeTestCase { @Override @@ -35,7 +36,7 @@ protected TestCase testData() { @Override protected Object convertedValue() { - return 1526955327123L * 1000L; + return TimeUnit.MILLISECONDS.toMicros(VALUE.getTime()); } static class TestCase extends TestData { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index e6e5aed10..b3f5f3d23 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,12 +16,10 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; -import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMicrosDeserializer; -import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampMillisDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; -import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMicrosSerializer; -import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampMillisSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; import org.apache.avro.reflect.AvroAlias; @@ -92,13 +90,13 @@ public Object findDeserializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMillisDeserializer.INSTANCE; + return AvroDateTimestampDeserializer.MILLIS; } } AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); if (timestampMicrosecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMicrosDeserializer.INSTANCE; + return AvroDateTimestampDeserializer.MICROS; } } AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); @@ -168,13 +166,13 @@ public Object findSerializer(Annotated a) { AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMillisSerializer.INSTANCE; + return AvroDateTimestampSerializer.MILLIS; } } AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); if (timestampMicrosecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampMicrosSerializer.INSTANCE; + return AvroDateTimestampSerializer.MICROS; } } AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java new file mode 100644 index 000000000..c0086420c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampDeserializer.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +public class AvroDateTimestampDeserializer extends JsonDeserializer { + public static final JsonDeserializer MILLIS = new AvroDateTimestampDeserializer(TimeUnit.MILLISECONDS); + public static final JsonDeserializer MICROS = new AvroDateTimestampDeserializer(TimeUnit.MICROSECONDS); + private final TimeUnit resolution; + + AvroDateTimestampDeserializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MICROSECONDS.toMillis(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + return new Date(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java deleted file mode 100644 index bde804621..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMicrosDeserializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.deser; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMicrosDeserializer extends JsonDeserializer { - public static JsonDeserializer INSTANCE = new AvroDateTimestampMicrosDeserializer(); - - @Override - public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - long value = jsonParser.getLongValue(); - - if (value == 0L) { - return new Date(value); - } - - return new Date(value / 1000L); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java deleted file mode 100644 index 53039802a..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimestampMillisDeserializer.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.deser; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMillisDeserializer extends JsonDeserializer { - public static JsonDeserializer INSTANCE = new AvroDateTimestampMillisDeserializer(); - - @Override - public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - long value = jsonParser.getLongValue(); - return new Date(value); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java deleted file mode 100644 index d9a0c2cbc..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMicrosSerializer.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMicrosSerializer extends JsonSerializer { - public static JsonSerializer INSTANCE = new AvroDateTimestampMicrosSerializer(); - - @Override - public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(date.getTime() * 1000L); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java deleted file mode 100644 index 0f3911612..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampMillisSerializer.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.ser; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.util.Date; - -public class AvroDateTimestampMillisSerializer extends JsonSerializer { - public static JsonSerializer INSTANCE = new AvroDateTimestampMillisSerializer(); - - @Override - public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeNumber(date.getTime()); - } -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java new file mode 100644 index 000000000..e40a42633 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimestampSerializer.java @@ -0,0 +1,39 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class AvroDateTimestampSerializer extends JsonSerializer { + public final static JsonSerializer MILLIS = new AvroDateTimestampSerializer(TimeUnit.MILLISECONDS); + public final static JsonSerializer MICROS = new AvroDateTimestampSerializer(TimeUnit.MICROSECONDS); + + private final TimeUnit resolution; + + AvroDateTimestampSerializer(TimeUnit resolution) { + this.resolution = resolution; + } + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final long input = date.getTime(); + final long output; + switch (this.resolution) { + case MICROSECONDS: + output = TimeUnit.MILLISECONDS.toMicros(input); + break; + case MILLISECONDS: + output = input; + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not supported", this.resolution) + ); + } + jsonGenerator.writeNumber(output); + } +} From 99b47708abf077a095c7076ba8141b6974c1825b Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 15:34:18 -0500 Subject: [PATCH 20/25] Moved decimals to use serializers like the other types. --- .../AvroJavaTimeAnnotationIntrospector.java | 3 +- .../logicaltypes/time/DateLocalDateTest.java | 6 +-- .../time/TimeMicrosLocalTimeTest.java | 5 +-- .../time/TimeMillisLocalTimeTest.java | 4 +- .../time/TimestampMicrosDateTest.java | 4 +- .../TimestampMicrosLocalDateTimeTest.java | 10 ++--- .../TimestampMicrosOffsetDateTimeTest.java | 10 ++--- .../TimestampMicrosZonedDateTimeTest.java | 12 +++--- .../time/TimestampMillisDateTest.java | 4 +- .../TimestampMillisLocalDateTimeTest.java | 10 ++--- .../TimestampMillisOffsetDateTimeTest.java | 11 +++-- .../TimestampMillisZonedDateTimeTest.java | 10 ++--- .../avro/AvroAnnotationIntrospector.java | 26 ++++++++++++ .../avro/deser/AvroDecimalDeserializer.java | 24 +++++++++++ .../avro/ser/AvroBytesDecimalSerializer.java | 27 ++++++++++++ .../avro/ser/AvroFixedDecimalSerializer.java | 42 +++++++++++++++++++ .../avro/ser/NonBSGenericDatumWriter.java | 19 --------- 17 files changed, 161 insertions(+), 66 deletions(-) create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java index 745b67a70..825dbaae5 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.dataformat.avro.AvroAnnotationIntrospector; import com.fasterxml.jackson.dataformat.avro.AvroDate; import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; @@ -25,7 +26,7 @@ import java.time.OffsetDateTime; import java.time.ZonedDateTime; -class AvroJavaTimeAnnotationIntrospector extends AnnotationIntrospector { +class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector { static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector(); @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java index fe5673aa1..38a2c3952 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -3,15 +3,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroDate; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.LocalDate; -import java.util.Date; public class DateLocalDateTest extends LogicalTypeTestCase { + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -27,8 +27,6 @@ protected String logicalType() { return "date"; } - static final LocalDate VALUE = LocalDate.of(2011, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java index 8c010ef1e..ae0c9f825 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -12,6 +11,8 @@ import java.util.concurrent.TimeUnit; public class TimeMicrosLocalTimeTest extends LogicalTypeTestCase { + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -27,8 +28,6 @@ protected String logicalType() { return "time-micros"; } - static final LocalTime VALUE = LocalTime.of(3, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java index 77ca03b6e..af8b43b49 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -11,6 +11,8 @@ import java.util.concurrent.TimeUnit; public class TimeMillisLocalTimeTest extends LogicalTypeTestCase { + static final LocalTime VALUE = LocalTime.of(3, 3, 14); + @Override protected Class dataClass() { return TestCase.class; @@ -26,8 +28,6 @@ protected String logicalType() { return "time-millis"; } - static final LocalTime VALUE = LocalTime.of(3, 3, 14); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index 93449ef82..aac3b9246 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -10,6 +10,8 @@ import java.util.concurrent.TimeUnit; public class TimestampMicrosDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(1526955327123L); + @Override protected Class dataClass() { return TestCase.class; @@ -25,8 +27,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final Date VALUE = new Date(1526955327123L); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java index 0fc78f652..11d576d23 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -15,6 +15,11 @@ import java.util.concurrent.TimeUnit; public class TimestampMicrosLocalDateTimeTest extends LogicalTypeTestCase { + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -30,11 +35,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java index bafaea035..fd277c42f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZoneId; public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase { + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index c39af3fb8..f26296aaf 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -3,18 +3,21 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class TimestampMicrosZonedDateTimeTest extends LogicalTypeTestCase { + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -30,11 +33,6 @@ protected String logicalType() { return "timestamp-micros"; } - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index 65f9d6444..c8117932a 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -10,6 +10,8 @@ import java.util.Date; public class TimestampMillisDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(1526955327123L); + @Override protected Class dataClass() { return TestCase.class; @@ -25,8 +27,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final Date VALUE = new Date(1526955327123L); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index 8e7526d91..ef4e617f8 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZoneId; public class TimestampMillisLocalDateTimeTest extends LogicalTypeTestCase { + static final LocalDateTime VALUE = LocalDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final LocalDateTime VALUE = LocalDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index b68002156..8a343467e 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -10,9 +10,13 @@ import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; -import java.time.ZonedDateTime; public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase { + static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -28,11 +32,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java index aa7e13e66..f01f545ff 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -11,6 +11,11 @@ import java.time.ZonedDateTime; public class TimestampMillisZonedDateTimeTest extends LogicalTypeTestCase { + static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(1526955327123L), + ZoneId.of("UTC") + ); + @Override protected Class dataClass() { return TestCase.class; @@ -26,11 +31,6 @@ protected String logicalType() { return "timestamp-millis"; } - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - @Override protected TestCase testData() { TestCase v = new TestCase(); diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index b3f5f3d23..d1f184b17 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -17,9 +17,12 @@ import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDecimalDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; +import com.fasterxml.jackson.dataformat.avro.ser.AvroBytesDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroFixedDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer; import org.apache.avro.reflect.AvroAlias; @@ -32,6 +35,7 @@ import org.apache.avro.reflect.Stringable; import org.apache.avro.reflect.Union; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -87,6 +91,13 @@ public Object findDeserializer(Annotated a) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); + if (decimal != null) { + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroDecimalDeserializer(decimal.scale()); + } + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -163,6 +174,21 @@ public Object findSerializer(Annotated a) { if (ann != null) { return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } + AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); + if (decimal != null) { + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + switch (decimal.schemaType()) { + case BYTES: + return new AvroBytesDecimalSerializer(decimal.scale()); + case FIXED: + return new AvroFixedDecimalSerializer(decimal.scale(), decimal.fixedSize()); + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported type for the logical type 'decimal'", decimal.schemaType()) + ); + } + } + } AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java new file mode 100644 index 000000000..91d0259c1 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDecimalDeserializer.java @@ -0,0 +1,24 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +public class AvroDecimalDeserializer extends JsonDeserializer { + private final int scale; + + public AvroDecimalDeserializer(int scale) { + this.scale = scale; + } + + @Override + public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final byte[] bytes = jsonParser.getBinaryValue(); + return new BigDecimal(new BigInteger(bytes), this.scale); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java new file mode 100644 index 000000000..37d6e9422 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroBytesDecimalSerializer.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.codehaus.jackson.JsonGenerationException; + +import java.io.IOException; +import java.math.BigDecimal; + +public class AvroBytesDecimalSerializer extends JsonSerializer { + final int scale; + + public AvroBytesDecimalSerializer(int scale) { + this.scale = scale; + } + + @Override + public void serialize(BigDecimal decimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (scale != decimal.scale()) { + throw new JsonGenerationException( + String.format("Cannot encode decimal with scale %s as scale %s.", decimal.scale(), scale) + ); + } + jsonGenerator.writeBinary(decimal.unscaledValue().toByteArray()); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java new file mode 100644 index 000000000..e73ac83a5 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroFixedDecimalSerializer.java @@ -0,0 +1,42 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.codehaus.jackson.JsonGenerationException; + +import java.io.IOException; +import java.math.BigDecimal; + +public class AvroFixedDecimalSerializer extends JsonSerializer { + final int scale; + final int fixedSize; + + public AvroFixedDecimalSerializer(int scale, int fixedSize) { + this.scale = scale; + this.fixedSize = fixedSize; + } + + @Override + public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (scale != value.scale()) { + throw new JsonGenerationException( + String.format("Cannot encode decimal with scale %s as scale %s.", value.scale(), scale) + ); + } + byte fillByte = (byte)(value.signum() < 0 ? 255 : 0); + byte[] unscaled = value.unscaledValue().toByteArray(); + byte[] bytes = new byte[this.fixedSize]; + int offset = bytes.length - unscaled.length; + + for(int i = 0; i < bytes.length; ++i) { + if (i < offset) { + bytes[i] = fillByte; + } else { + bytes[i] = unscaled[i - offset]; + } + } + + jsonGenerator.writeBinary(bytes); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java index 90e3156aa..1bd39fa73 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java @@ -61,26 +61,7 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out); return; case FIXED: - if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { - super.writeWithoutConversion( - schema, - DECIMAL_CONVERSION.toFixed(((BigDecimal) datum), schema, schema.getLogicalType()), - out - ); - return; - } - super.writeWithoutConversion(schema, datum, out); - return; case BYTES: - //TODO: This is ugly and I don't like the string check. - if(null!=schema.getLogicalType() && "decimal".equals(schema.getLogicalType().getName())) { - super.writeWithoutConversion( - schema, - DECIMAL_CONVERSION.toBytes(((BigDecimal) datum), schema, schema.getLogicalType()), - out - ); - return; - } super.writeWithoutConversion(schema, datum, out); return; case INT: From 45971412fd0fc137bae3e4f26823d7050fa2474a Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 15:43:25 -0500 Subject: [PATCH 21/25] Removed direct avro dependency. We're already getting this from `jackson-dataformat-avro` --- avro-java8/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml index ee763eeb2..403e1d8b4 100644 --- a/avro-java8/pom.xml +++ b/avro-java8/pom.xml @@ -36,11 +36,6 @@ abstractions. jackson-dataformat-avro ${project.version} - - org.apache.avro - avro - 1.8.1 - From 41c0344d2098ac7bc2eca93b6cd8e799d14e011f Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Thu, 24 May 2018 17:09:02 -0500 Subject: [PATCH 22/25] Added support for java.util.Date for logical types `date`, `time-millis`, and `time-micros`. --- .../java8/logicaltypes/time/DateDateTest.java | 60 ++++++++++++++++++ .../logicaltypes/time/TimeMicrosDateTest.java | 62 ++++++++++++++++++ .../logicaltypes/time/TimeMillisDateTest.java | 63 +++++++++++++++++++ .../avro/AvroAnnotationIntrospector.java | 44 ++++++++++++- .../avro/deser/AvroDateDateDeserializer.java | 25 ++++++++ .../avro/deser/AvroDateTimeDeserializer.java | 36 +++++++++++ .../avro/ser/AvroDateDateSerializer.java | 29 +++++++++ .../avro/ser/AvroDateTimeSerializer.java | 40 ++++++++++++ 8 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java create mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java new file mode 100644 index 000000000..c5cc181a7 --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class DateDateTest extends LogicalTypeTestCase { + static final LocalDate VALUE = LocalDate.of(2011, 3, 14); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "date"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = new Date(TimeUnit.DAYS.toMillis(VALUE.toEpochDay())); + return v; + } + + @Override + protected Object convertedValue() { + return VALUE.toEpochDay(); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroDate + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java new file mode 100644 index 000000000..2348333fa --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java @@ -0,0 +1,62 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class TimeMicrosDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(8127123L); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + LocalTime time = VALUE.toInstant().atOffset(ZoneOffset.UTC).toLocalTime(); + return TimeUnit.NANOSECONDS.toMillis(time.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java new file mode 100644 index 000000000..f43da72ca --- /dev/null +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; +import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; +import org.apache.avro.Schema; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class TimeMillisDateTest extends LogicalTypeTestCase { + static final Date VALUE = new Date(8127123L); + + @Override + protected Class dataClass() { + return TestCase.class; + } + + @Override + protected Schema.Type schemaType() { + return Schema.Type.INT; + } + + @Override + protected String logicalType() { + return "time-millis"; + } + + @Override + protected TestCase testData() { + TestCase v = new TestCase(); + v.value = VALUE; + return v; + } + + @Override + protected Object convertedValue() { + LocalTime time = VALUE.toInstant().atOffset(ZoneOffset.UTC).toLocalTime(); + return TimeUnit.NANOSECONDS.toMillis(time.toNanoOfDay()); + } + + @Override + protected void configure(AvroMapper mapper) { + + } + + static class TestCase extends TestData { + @JsonProperty(required = true) + @AvroTimeMillisecond + Date value; + + @Override + public Date value() { + return this.value; + } + } + +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index d1f184b17..7e3b87922 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -16,11 +16,15 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.dataformat.avro.apacheimpl.CustomEncodingDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateDateDeserializer; +import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimeDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDateTimestampDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroDecimalDeserializer; import com.fasterxml.jackson.dataformat.avro.deser.AvroUUIDDeserializer; import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper; import com.fasterxml.jackson.dataformat.avro.ser.AvroBytesDecimalSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateDateSerializer; +import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimeSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroDateTimestampSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroFixedDecimalSerializer; import com.fasterxml.jackson.dataformat.avro.ser.AvroUUIDSerializer; @@ -97,7 +101,18 @@ public Object findDeserializer(Annotated a) { return new AvroDecimalDeserializer(decimal.scale()); } } - + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (timeMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MILLIS; + } + } + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (timeMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MICROS; + } + } AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -116,7 +131,12 @@ public Object findDeserializer(Annotated a) { return AvroUUIDDeserializer.INSTANCE; } } - + AvroDate avroDate = _findAnnotation(a, AvroDate.class); + if (avroDate != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateDeserializer.INSTANCE; + } + } return null; } @@ -189,6 +209,19 @@ public Object findSerializer(Annotated a) { } } } + AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); + if (timeMillisecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MILLIS; + } + } + AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); + if (timeMicrosecond != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MICROS; + } + } + AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); if (timestampMillisecond != null) { if (a.getRawType().isAssignableFrom(Date.class)) { @@ -207,7 +240,12 @@ public Object findSerializer(Annotated a) { return AvroUUIDSerializer.INSTANCE; } } - + AvroDate avroDate = _findAnnotation(a, AvroDate.class); + if (avroDate != null) { + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateSerializer.INSTANCE; + } + } return null; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java new file mode 100644 index 000000000..3b7df965c --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateDateDeserializer.java @@ -0,0 +1,25 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; + + +public class AvroDateDateDeserializer extends JsonDeserializer { + public static final JsonDeserializer INSTANCE = new AvroDateDateDeserializer(); + private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; + + AvroDateDateDeserializer() { + + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + return new java.util.Date(input * MILLIS_PER_DAY); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java new file mode 100644 index 000000000..1efbc575e --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroDateTimeDeserializer.java @@ -0,0 +1,36 @@ +package com.fasterxml.jackson.dataformat.avro.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +public class AvroDateTimeDeserializer extends JsonDeserializer { + public static final JsonDeserializer MILLIS = new AvroDateTimeDeserializer(TimeUnit.MILLISECONDS); + public static final JsonDeserializer MICROS = new AvroDateTimeDeserializer(TimeUnit.MICROSECONDS); + private final TimeUnit resolution; + private final long max; + + AvroDateTimeDeserializer(TimeUnit resolution) { + this.resolution = resolution; + this.max = this.resolution.convert(86400, TimeUnit.SECONDS); + } + + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final long input = jsonParser.getLongValue(); + + if (input < 0 || input > this.max) { + throw new IllegalStateException( + String.format("Value must be between 0 and %s %s(s).", this.max, this.resolution) + ); + } + final long output = TimeUnit.MILLISECONDS.convert(input, this.resolution); + return new Date(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java new file mode 100644 index 000000000..ea8ef1adc --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateDateSerializer.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class AvroDateDateSerializer extends JsonSerializer { + public static final JsonSerializer INSTANCE = new AvroDateDateSerializer(); + private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; + + @Override + public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + Calendar calendar = Calendar.getInstance(UTC); + calendar.setTime(value); + if (calendar.get(Calendar.HOUR_OF_DAY) != 0 || calendar.get(Calendar.MINUTE) != 0 || + calendar.get(Calendar.SECOND) != 0 || calendar.get(Calendar.MILLISECOND) != 0) { + throw new IllegalStateException("Date type should not have any time fields set to non-zero values."); + } + long unixMillis = calendar.getTimeInMillis(); + int output =(int) (unixMillis / MILLIS_PER_DAY); + jsonGenerator.writeNumber(output); + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java new file mode 100644 index 000000000..bedf4afde --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroDateTimeSerializer.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.dataformat.avro.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class AvroDateTimeSerializer extends JsonSerializer { + public final static JsonSerializer MILLIS = new AvroDateTimeSerializer(TimeUnit.MILLISECONDS); + public final static JsonSerializer MICROS = new AvroDateTimeSerializer(TimeUnit.MICROSECONDS); + private final static Date MIN_DATE = new Date(0L); + private final static Date MAX_DATE = new Date( + TimeUnit.SECONDS.toMillis(86400) + ); + + private final TimeUnit resolution; + private final long max; + + + AvroDateTimeSerializer(TimeUnit resolution) { + this.resolution = resolution; + this.max = this.resolution.convert(86400, TimeUnit.SECONDS); + } + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + final long input = date.getTime(); + + if (input < 0 || input > this.max) { + throw new IllegalStateException( + String.format("Value must be between %s and %s.", MIN_DATE, MAX_DATE) + ); + } + final long output = this.resolution.convert(input, TimeUnit.MILLISECONDS); + jsonGenerator.writeNumber(output); + } +} From 17ff44ad01cfd84894335569a55e10850303d778 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Wed, 30 May 2018 17:54:31 -0700 Subject: [PATCH 23/25] Refactored to use a single attribute. Deprecated AvroFixedSize attribute for a single AvroType attribute that can handle all types. --- .../AvroJavaTimeAnnotationIntrospector.java | 176 ++++++++---------- .../java8/logicaltypes/BytesDecimalTest.java | 4 +- .../java8/logicaltypes/FixedDecimalTest.java | 4 +- .../avro/java8/logicaltypes/UUIDTest.java | 7 +- .../java8/logicaltypes/time/DateDateTest.java | 4 +- .../logicaltypes/time/DateLocalDateTest.java | 4 +- .../logicaltypes/time/TimeMicrosDateTest.java | 4 +- .../time/TimeMicrosLocalTimeTest.java | 4 +- .../logicaltypes/time/TimeMillisDateTest.java | 6 +- .../time/TimeMillisLocalTimeTest.java | 4 +- .../time/TimestampMicrosDateTest.java | 4 +- .../TimestampMicrosLocalDateTimeTest.java | 4 +- .../TimestampMicrosOffsetDateTimeTest.java | 4 +- .../TimestampMicrosZonedDateTimeTest.java | 4 +- .../time/TimestampMillisDateTest.java | 4 +- .../TimestampMillisLocalDateTimeTest.java | 5 +- .../TimestampMillisOffsetDateTimeTest.java | 4 +- .../TimestampMillisZonedDateTimeTest.java | 4 +- .../avro/AvroAnnotationIntrospector.java | 166 ++++++++--------- .../jackson/dataformat/avro/AvroDate.java | 18 -- .../jackson/dataformat/avro/AvroDecimal.java | 48 ----- .../dataformat/avro/AvroFixedSize.java | 1 + .../dataformat/avro/AvroTimeMicrosecond.java | 18 -- .../dataformat/avro/AvroTimeMillisecond.java | 18 -- .../avro/AvroTimestampMicrosecond.java | 18 -- .../avro/AvroTimestampMillisecond.java | 18 -- .../jackson/dataformat/avro/AvroType.java | 58 ++++++ .../jackson/dataformat/avro/AvroUUID.java | 19 -- .../dataformat/avro/schema/RecordVisitor.java | 162 ++++++++-------- .../avro/schema/TestLogicalTypes.java | 39 ++-- .../avro/schema/TestSimpleGeneration.java | 34 ++++ 31 files changed, 380 insertions(+), 487 deletions(-) delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java create mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java delete mode 100644 avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java index 825dbaae5..ed3db6d78 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -1,14 +1,9 @@ package com.fasterxml.jackson.dataformat.avro.java8; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.dataformat.avro.AvroAnnotationIntrospector; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalTimeDeserializer; @@ -31,103 +26,94 @@ class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector { @Override public Object findSerializer(Annotated a) { - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (null != timestampMillisecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeSerializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MILLIS; - } - } - - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (null != timestampMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeSerializer.MICROS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MICROS; - } - } - - AvroDate date = _findAnnotation(a, AvroDate.class); - if (null != date) { - if (a.getRawType().isAssignableFrom(LocalDate.class)) { - return LocalDateSerializer.INSTANCE; - } - } - - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (null != timeMillisecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeSerializer.MILLIS; - } - } - - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (null != timeMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeSerializer.MICROS; + AvroType logicalType = _findAnnotation(a, AvroType.class); + if (null != logicalType) { + switch (logicalType.logicalType()) { + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MILLIS; + } + break; + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeSerializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeSerializer.MICROS; + } + break; + case DATE: + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateSerializer.INSTANCE; + } + break; + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MILLIS; + } + break; + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeSerializer.MICROS; + } + break; } } return super.findSerializer(a); - } @Override public Object findDeserializer(Annotated a) { - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (null != timestampMillisecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeDeserializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MILLIS; - } - } - - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (null != timestampMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { - return LocalDateTimeDeserializer.MICROS; - } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MICROS; - } - } - - AvroDate date = _findAnnotation(a, AvroDate.class); - if (null != date) { - if (a.getRawType().isAssignableFrom(LocalDate.class)) { - return LocalDateDeserializer.INSTANCE; - } - } - - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (null != timeMillisecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeDeserializer.MILLIS; - } - } - - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (null != timeMicrosecond) { - if (a.getRawType().isAssignableFrom(LocalTime.class)) { - return LocalTimeDeserializer.MICROS; + AvroType logicalType = _findAnnotation(a, AvroType.class); + if (null != logicalType) { + switch (logicalType.logicalType()) { + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MILLIS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MILLIS; + } + break; + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { + return LocalDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { + return OffsetDateTimeDeserializer.MICROS; + } + if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { + return ZonedDateTimeDeserializer.MICROS; + } + break; + case DATE: + if (a.getRawType().isAssignableFrom(LocalDate.class)) { + return LocalDateDeserializer.INSTANCE; + } + break; + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MILLIS; + } + break; + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(LocalTime.class)) { + return LocalTimeDeserializer.MICROS; + } + break; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java index 6b3fffce0..a9a92fca1 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Conversions; import org.apache.avro.Schema; @@ -39,7 +39,7 @@ protected Object convertedValue() { static class BytesDecimal extends TestData { @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3) + @AvroType(precision = 3, scale = 3, schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java index 410b9b606..a8c23c926 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Conversions; import org.apache.avro.Schema; @@ -39,7 +39,7 @@ protected Object convertedValue() { static class FixedDecimal extends TestData { @JsonProperty(required = true) - @AvroDecimal(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED) + @AvroType(precision = 3, scale = 3, fixedSize = 8, typeNamespace = "com.foo.example", typeName = "Decimal", schemaType = Schema.Type.FIXED, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java index 69c875d8f..6a54b0d90 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java @@ -1,12 +1,9 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; -import org.apache.avro.Conversions; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.Schema; -import java.math.BigDecimal; import java.util.UUID; public class UUIDTest extends LogicalTypeTestCase { @@ -41,7 +38,7 @@ protected Object convertedValue() { static class UUIDTestCase extends TestData { @JsonProperty(required = true) - @AvroUUID + @AvroType(schemaType = Schema.Type.STRING, logicalType = AvroType.LogicalType.UUID) public UUID value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java index c5cc181a7..a490a65d0 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -48,7 +48,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java index 38a2c3952..a0079c368 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroDate; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -46,7 +46,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) LocalDate value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java index 2348333fa..a5cb4061f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -50,7 +50,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java index ae0c9f825..92a2cb4bf 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -47,7 +47,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND) LocalTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java index f43da72ca..db22f1419 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java @@ -1,13 +1,12 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; -import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneOffset; import java.util.Date; @@ -51,7 +50,8 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) + Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java index af8b43b49..ec5fd4ac8 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -47,7 +47,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) LocalTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java index aac3b9246..c94f9aa25 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -41,7 +41,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java index 11d576d23..bdd4dd54d 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosLocalDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -55,7 +55,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) LocalDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java index fd277c42f..8910a743c 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -46,7 +46,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) OffsetDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java index f26296aaf..0dccaf944 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; @@ -52,7 +52,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) ZonedDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java index c8117932a..efd80196f 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -46,7 +46,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) Date value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java index ef4e617f8..0d44139e6 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -45,7 +45,8 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) + LocalDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java index 8a343467e..db2e9ee8c 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -51,7 +51,7 @@ protected void configure(AvroMapper mapper) { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) OffsetDateTime value; @Override diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java index f01f545ff..a3d4798e6 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; @@ -45,7 +45,7 @@ protected Object convertedValue() { static class TestCase extends TestData { @JsonProperty(required = true) - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) ZonedDateTime value; @Override diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java index 7e3b87922..b2f1055ee 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java @@ -95,46 +95,38 @@ public Object findDeserializer(Annotated a) { return new CustomEncodingDeserializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } - AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); - if (decimal != null) { - if (a.getRawType().isAssignableFrom(BigDecimal.class)) { - return new AvroDecimalDeserializer(decimal.scale()); - } - } - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (timeMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeDeserializer.MILLIS; - } - } - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (timeMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeDeserializer.MICROS; - } - } - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (timestampMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampDeserializer.MILLIS; - } - } - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (timestampMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampDeserializer.MICROS; - } - } - AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); - if (avroUUID != null) { - if (a.getRawType().isAssignableFrom(UUID.class)) { - return AvroUUIDDeserializer.INSTANCE; - } - } - AvroDate avroDate = _findAnnotation(a, AvroDate.class); - if (avroDate != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateDateDeserializer.INSTANCE; + AvroType logicalType = _findAnnotation(a, AvroType.class); + + if(null != logicalType) { + switch (logicalType.logicalType()) { + case DECIMAL: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroDecimalDeserializer(logicalType.scale()); + } + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MILLIS; + } + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampDeserializer.MILLIS; + } + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeDeserializer.MICROS; + } + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampDeserializer.MICROS; + } + case UUID: + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDDeserializer.INSTANCE; + } + case DATE: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateDeserializer.INSTANCE; + } } } return null; @@ -194,58 +186,52 @@ public Object findSerializer(Annotated a) { if (ann != null) { return new CustomEncodingSerializer<>((CustomEncoding) ClassUtil.createInstance(ann.using(), true)); } - AvroDecimal decimal = _findAnnotation(a, AvroDecimal.class); - if (decimal != null) { - if (a.getRawType().isAssignableFrom(BigDecimal.class)) { - switch (decimal.schemaType()) { - case BYTES: - return new AvroBytesDecimalSerializer(decimal.scale()); - case FIXED: - return new AvroFixedDecimalSerializer(decimal.scale(), decimal.fixedSize()); - default: - throw new UnsupportedOperationException( - String.format("%s is not a supported type for the logical type 'decimal'", decimal.schemaType()) - ); - } - } - } - AvroTimeMillisecond timeMillisecond = _findAnnotation(a, AvroTimeMillisecond.class); - if (timeMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeSerializer.MILLIS; - } - } - AvroTimeMicrosecond timeMicrosecond = _findAnnotation(a, AvroTimeMicrosecond.class); - if (timeMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimeSerializer.MICROS; - } - } - - AvroTimestampMillisecond timestampMillisecond = _findAnnotation(a, AvroTimestampMillisecond.class); - if (timestampMillisecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampSerializer.MILLIS; - } - } - AvroTimestampMicrosecond timestampMicrosecond = _findAnnotation(a, AvroTimestampMicrosecond.class); - if (timestampMicrosecond != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateTimestampSerializer.MICROS; - } - } - AvroUUID avroUUID = _findAnnotation(a, AvroUUID.class); - if (avroUUID != null) { - if (a.getRawType().isAssignableFrom(UUID.class)) { - return AvroUUIDSerializer.INSTANCE; - } - } - AvroDate avroDate = _findAnnotation(a, AvroDate.class); - if (avroDate != null) { - if (a.getRawType().isAssignableFrom(Date.class)) { - return AvroDateDateSerializer.INSTANCE; + AvroType logicalType = _findAnnotation(a, AvroType.class); + + if(null != logicalType) { + switch (logicalType.logicalType()) { + case DECIMAL: + switch (logicalType.schemaType()) { + case FIXED: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroFixedDecimalSerializer(logicalType.scale(), logicalType.fixedSize()); + } + case BYTES: + if (a.getRawType().isAssignableFrom(BigDecimal.class)) { + return new AvroBytesDecimalSerializer(logicalType.scale()); + } + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported type for the logical type 'decimal'", logicalType.schemaType()) + ); + } + case TIME_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MILLIS; + } + case TIMESTAMP_MILLISECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampSerializer.MILLIS; + } + case TIME_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimeSerializer.MICROS; + } + case TIMESTAMP_MICROSECOND: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateTimestampSerializer.MICROS; + } + case UUID: + if (a.getRawType().isAssignableFrom(UUID.class)) { + return AvroUUIDSerializer.INSTANCE; + } + case DATE: + if (a.getRawType().isAssignableFrom(Date.class)) { + return AvroDateDateSerializer.INSTANCE; + } } } + return null; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java deleted file mode 100644 index 0632781c1..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDate.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroDate { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java deleted file mode 100644 index d651ac9f8..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroDecimal.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import org.apache.avro.Schema; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroDecimal { - /** - * - */ - Schema.Type schemaType() default Schema.Type.BYTES; - - /** - * The name of the type in the generated schema - */ - String typeName() default ""; - - /** - * The namespace of the type in the generated schema (optional) - */ - String typeNamespace() default ""; - - /** - * The size when the schemaType is FIXED. - */ - int fixedSize() default 0; - /** - * The maximum precision of decimals stored in this type. - */ - int precision(); - - /** - * - * @return - */ - int scale() default 0; -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java index 81210aa31..d5ed22b7f 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroFixedSize.java @@ -10,6 +10,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) +@Deprecated public @interface AvroFixedSize { /** * The name of the type in the generated schema diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java deleted file mode 100644 index aa346d038..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMicrosecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimeMicrosecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java deleted file mode 100644 index e084f050b..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimeMillisecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimeMillisecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java deleted file mode 100644 index 098391ebe..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMicrosecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimestampMicrosecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java deleted file mode 100644 index 414568241..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroTimestampMillisecond.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroTimestampMillisecond { - -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java new file mode 100644 index 000000000..53566ee76 --- /dev/null +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroType.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.dataformat.avro; + +import org.apache.avro.Schema; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface AvroType { + /** + * + */ + Schema.Type schemaType(); + + /** + * + */ + LogicalType logicalType() default LogicalType.NONE; + + /** + * The name of the type in the generated schema + */ + String typeName() default ""; + + /** + * The namespace of the type in the generated schema (optional) + */ + String typeNamespace() default ""; + + /** + * The size when the schemaType is FIXED. + */ + int fixedSize() default 0; + /** + * The maximum precision of decimals stored in this type. + */ + int precision() default 0; + + /** + * + * @return + */ + int scale() default 0; + + enum LogicalType { + DECIMAL, + DATE, + TIME_MICROSECOND, + TIMESTAMP_MICROSECOND, + TIME_MILLISECOND, + TIMESTAMP_MILLISECOND, + UUID, + NONE + } +} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java deleted file mode 100644 index 27acd79f5..000000000 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroUUID.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro; - -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Only used during Avro schema generation; has no effect on data (de)serialization. - *

    - * Instructs the {@link AvroSchemaGenerator AvroSchemaGenerator} - * to declare the annotated property as type "fixed" ({@link org.apache.avro.Schema.Type#FIXED Schema.Type.FIXED}). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface AvroUUID { -} diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java index 9201099e9..3c2bb9ba5 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java @@ -5,13 +5,7 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; +import com.fasterxml.jackson.dataformat.avro.AvroType; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; @@ -44,9 +38,9 @@ public class RecordVisitor protected final boolean _overridden; protected Schema _avroSchema; - + protected List _fields = new ArrayList(); - + public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas) { super(p); @@ -83,7 +77,7 @@ public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas } schemas.addSchema(type, _avroSchema); } - + @Override public Schema builtAvroSchema() { if (!_overridden) { @@ -98,7 +92,7 @@ public Schema builtAvroSchema() { /* JsonObjectFormatVisitor implementation /********************************************************** */ - + @Override public void property(BeanProperty writer) throws JsonMappingException { @@ -150,7 +144,7 @@ public void optionalProperty(String name, JsonFormatVisitable handler, /* Internal methods /********************************************************************** */ - + protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) throws JsonMappingException { Schema writerSchema; @@ -160,86 +154,80 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) Schema.Parser parser = new Schema.Parser(); writerSchema = parser.parse(schemaOverride.value()); } else { - AvroFixedSize fixedSize = prop.getAnnotation(AvroFixedSize.class); - if (fixedSize != null) { - writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); - } else { - AvroDecimal decimal = prop.getAnnotation(AvroDecimal.class); - if(decimal!= null) { - LogicalTypes.Decimal d = LogicalTypes.decimal(decimal.precision(), decimal.scale()); - Schema s; - if(Type.BYTES == decimal.schemaType()) { - s = Schema.create(Type.BYTES); - } else if(Type.FIXED == decimal.schemaType()) { - s = Schema.createFixed(decimal.typeName(), - null, - decimal.typeNamespace(), - decimal.fixedSize() - ); - } else { - throw new IllegalStateException("Avro schema type must be BYTES or FIXED."); - } - writerSchema = d.addToSchema(s); - d.validate(writerSchema); + AvroType type = prop.getAnnotation(AvroType.class); + + if(null != type) { + if(type.schemaType() == Type.FIXED) { + writerSchema = Schema.createFixed(type.typeName(), + null, + type.typeNamespace(), + type.fixedSize() + ); } else { - AvroTimestampMillisecond timestampMillisecond = prop.getAnnotation(AvroTimestampMillisecond.class); - if(timestampMillisecond != null) { + writerSchema = Schema.create(type.schemaType()); + } + switch (type.logicalType()) { + case NONE: + break; + case DATE: + writerSchema = LogicalTypes.date() + .addToSchema(writerSchema); + break; + case UUID: + writerSchema = LogicalTypes.uuid() + .addToSchema(writerSchema); + break; + case DECIMAL: + writerSchema = LogicalTypes.decimal(type.precision(), type.scale()) + .addToSchema(writerSchema); + break; + case TIME_MICROSECOND: + writerSchema = LogicalTypes.timeMicros() + .addToSchema(writerSchema); + break; + case TIME_MILLISECOND: + writerSchema = LogicalTypes.timeMillis() + .addToSchema(writerSchema); + break; + case TIMESTAMP_MICROSECOND: + writerSchema = LogicalTypes.timestampMicros() + .addToSchema(writerSchema); + break; + case TIMESTAMP_MILLISECOND: writerSchema = LogicalTypes.timestampMillis() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroTimestampMicrosecond timestampMicrosecond = prop.getAnnotation(AvroTimestampMicrosecond.class); - if(timestampMicrosecond!=null){ - writerSchema = LogicalTypes.timestampMicros() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroDate date = prop.getAnnotation(AvroDate.class); - if(date!=null){ - writerSchema = LogicalTypes.date() - .addToSchema(Schema.create(Type.INT)); - } else { - AvroTimeMillisecond timeMillisecond = prop.getAnnotation(AvroTimeMillisecond.class); - if(timeMillisecond!=null) { - writerSchema = LogicalTypes.timeMillis() - .addToSchema(Schema.create(Type.INT)); - } else { - AvroTimeMicrosecond timeMicrosecond = prop.getAnnotation(AvroTimeMicrosecond.class); - if(timeMicrosecond!=null) { - writerSchema = LogicalTypes.timeMicros() - .addToSchema(Schema.create(Type.LONG)); - } else { - AvroUUID avroUUID = prop.getAnnotation(AvroUUID.class); - - if(avroUUID != null) { - writerSchema = LogicalTypes.uuid() - .addToSchema(Schema.create(Type.STRING)); - } else { - JsonSerializer ser = null; + .addToSchema(writerSchema); + break; + default: + throw new UnsupportedOperationException( + String.format("%s is not a supported logical type.", type.logicalType()) + ); + } + } else { + AvroFixedSize fixedSize = prop.getAnnotation(AvroFixedSize.class); + if (fixedSize != null) { + writerSchema = Schema.createFixed(fixedSize.typeName(), null, fixedSize.typeNamespace(), fixedSize.size()); + } else { + JsonSerializer ser = null; - // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... - if (prop instanceof BeanPropertyWriter) { - BeanPropertyWriter bpw = (BeanPropertyWriter) prop; - ser = bpw.getSerializer(); - /* - * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly - */ - optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema - } - final SerializerProvider prov = getProvider(); - if (ser == null) { - if (prov == null) { - throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); - } - ser = prov.findValueSerializer(prop.getType(), prop); - } - VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); - ser.acceptJsonFormatVisitor(visitor, prop.getType()); - writerSchema = visitor.getAvroSchema(); - } - } - } - } + // 23-Nov-2012, tatu: Ideally shouldn't need to do this but... + if (prop instanceof BeanPropertyWriter) { + BeanPropertyWriter bpw = (BeanPropertyWriter) prop; + ser = bpw.getSerializer(); + /* + * 2-Mar-2017, bryan: AvroEncode annotation expects to have the schema used directly + */ + optional = optional && !(ser instanceof CustomEncodingSerializer); // Don't modify schema + } + final SerializerProvider prov = getProvider(); + if (ser == null) { + if (prov == null) { + throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor"); } + ser = prov.findValueSerializer(prop.getType(), prop); } + VisitorFormatWrapperImpl visitor = new VisitorFormatWrapperImpl(_schemas, prov); + ser.acceptJsonFormatVisitor(visitor, prop.getType()); + writerSchema = visitor.getAvroSchema(); } } diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java index 3de11d390..faaac0ca9 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java @@ -2,17 +2,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.dataformat.avro.AvroDate; -import com.fasterxml.jackson.dataformat.avro.AvroDecimal; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; import com.fasterxml.jackson.dataformat.avro.AvroSchema; import com.fasterxml.jackson.dataformat.avro.AvroTestBase; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimeMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMicrosecond; -import com.fasterxml.jackson.dataformat.avro.AvroTimestampMillisecond; -import com.fasterxml.jackson.dataformat.avro.AvroUUID; -import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.junit.Assert; @@ -25,61 +18,67 @@ public class TestLogicalTypes extends AvroTestBase { static class BytesDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5) + @AvroType(schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL, precision = 5) public BigDecimal value; } static class FixedNoNameDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED) + @AvroType(precision = 5, schemaType = Schema.Type.FIXED, logicalType = AvroType.LogicalType.DECIMAL) public BigDecimal value; } static class FixedDecimalType { @JsonProperty(required = true) - @AvroDecimal(precision = 5, schemaType = Schema.Type.FIXED, typeName = "foo", typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", fixedSize = 8) + @AvroType(precision = 5, + schemaType = Schema.Type.FIXED, + typeName = "foo", + typeNamespace = "com.fasterxml.jackson.dataformat.avro.schema", + fixedSize = 8, + logicalType = AvroType.LogicalType.DECIMAL + ) public BigDecimal value; } static class TimestampMillisecondsType { - @AvroTimestampMillisecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) @JsonProperty(required = true) public Date value; } static class TimeMillisecondsType { - @AvroTimeMillisecond + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND) @JsonProperty(required = true) public Date value; } static class TimestampMicrosecondsType { - @AvroTimestampMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) @JsonProperty(required = true) public Date value; } static class TimeMicrosecondsType { - @AvroTimeMicrosecond + @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND) @JsonProperty(required = true) public Date value; } - + static class DateType { - @AvroDate + @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE) @JsonProperty(required = true) public Date value; } static class UUIDType { - @AvroUUID + @AvroType(schemaType = Schema.Type.STRING, logicalType = AvroType.LogicalType.UUID) @JsonProperty(required = true) public UUID value; } AvroSchema getSchema(Class cls) throws JsonMappingException { AvroMapper avroMapper = new AvroMapper(); - AvroSchemaGenerator avroSchemaGenerator=new AvroSchemaGenerator(); + AvroSchemaGenerator avroSchemaGenerator = new AvroSchemaGenerator(); avroMapper.acceptJsonFormatVisitor(cls, avroSchemaGenerator); AvroSchema schema = avroSchemaGenerator.getGeneratedSchema(); assertNotNull("Schema should not be null.", schema); @@ -154,7 +153,7 @@ public void testTimeMicrosecondsType() throws JsonMappingException { Schema.Field field = schema.getField("value"); assertLogicalType(field, Schema.Type.LONG, "time-micros"); } - + public void testDateType() throws JsonMappingException { AvroSchema avroSchema = getSchema(DateType.class); Schema schema = avroSchema.getAvroSchema(); diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java index 84948f64f..61c96cff8 100644 --- a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestSimpleGeneration.java @@ -31,6 +31,27 @@ static class WithDate { public Date date; } + static class WithAvroTypeFixed { + @JsonProperty(required = true) + @AvroType(typeName = "FixedFieldBytes", fixedSize = 4, schemaType = Schema.Type.FIXED) + public byte[] fixedField; + + @JsonProperty(value = "wff", required = true) + @AvroType(typeName = "WrappedFixedFieldBytes", fixedSize = 8, schemaType = Schema.Type.FIXED) + public WithFixedField.WrappedByteArray wrappedFixedField; + + void setValue(byte[] bytes) { + this.fixedField = bytes; + } + + static class WrappedByteArray { + @JsonValue + public ByteBuffer getBytes() { + return null; + } + } + } + static class WithFixedField { @JsonProperty(required = true) @AvroFixedSize(typeName = "FixedFieldBytes", size = 4) @@ -158,6 +179,19 @@ public void testFixed() throws Exception assertEquals(8, wrappedFieldSchema.getFixedSize()); } + public void testFixedAvroType() throws Exception + { + AvroSchemaGenerator gen = new AvroSchemaGenerator(); + MAPPER.acceptJsonFormatVisitor(WithAvroTypeFixed.class, gen); + Schema generated = gen.getAvroSchema(); + Schema fixedFieldSchema = generated.getField("fixedField").schema(); + assertEquals(Schema.Type.FIXED, fixedFieldSchema.getType()); + assertEquals(4, fixedFieldSchema.getFixedSize()); + + Schema wrappedFieldSchema = generated.getField("wff").schema(); + assertEquals(Schema.Type.FIXED, wrappedFieldSchema.getType()); + assertEquals(8, wrappedFieldSchema.getFixedSize()); + } // as per [dataformats-binary#98], no can do (unless we start supporting polymorphic // handling or something...) public void testSchemaForUntypedMap() throws Exception From 9492fc4080d53676eafca6f7724eb0b1710ef7a9 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Fri, 23 Aug 2019 18:02:50 -0500 Subject: [PATCH 24/25] Bump version to 2.10.0.pr2-SNAPSHOT. --- avro-java8/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml index 403e1d8b4..6afe15f0f 100644 --- a/avro-java8/pom.xml +++ b/avro-java8/pom.xml @@ -4,7 +4,7 @@ com.fasterxml.jackson.dataformat jackson-dataformats-binary - 2.9.6-SNAPSHOT + 2.10.0.pr2-SNAPSHOT jackson-dataformat-avro-java8 Jackson dataformat: Avro Java 8 From b1d374dbca1829a1ceff742f4041ba4385b8c3a1 Mon Sep 17 00:00:00 2001 From: Jeremy Custenborder Date: Fri, 23 Aug 2019 18:27:52 -0500 Subject: [PATCH 25/25] Removed support for ZonedDateTime and OffsetDateTime because there are no corresponding logical types for Avro. Added support for storing Instants. --- .../AvroJavaTimeAnnotationIntrospector.java | 37 ++++------- .../java8/deser/BaseTimeJsonDeserializer.java | 12 ++-- .../avro/java8/deser/InstantDeserializer.java | 20 ++++++ .../deser/OffsetDateTimeDeserializer.java | 21 ------ .../deser/ZonedDateTimeDeserializer.java | 21 ------ .../java8/ser/BaseTimeJsonSerializer.java | 3 +- .../avro/java8/ser/InstantSerializer.java | 20 ++++++ .../java8/ser/OffsetDateTimeSerializer.java | 21 ------ .../java8/ser/ZonedDateTimeSerializer.java | 21 ------ ...t.java => TimestampMicrosInstantTest.java} | 21 +++--- .../TimestampMicrosZonedDateTimeTest.java | 64 ------------------- ...t.java => TimestampMillisInstantTest.java} | 18 ++---- .../TimestampMillisZonedDateTimeTest.java | 57 ----------------- 13 files changed, 76 insertions(+), 260 deletions(-) create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/InstantDeserializer.java delete mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java delete mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java create mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/InstantSerializer.java delete mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java delete mode 100644 avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java rename avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/{TimestampMicrosOffsetDateTimeTest.java => TimestampMicrosInstantTest.java} (68%) delete mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java rename avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/{TimestampMillisOffsetDateTimeTest.java => TimestampMillisInstantTest.java} (72%) delete mode 100644 avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java index ed3db6d78..ec0dd0532 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java @@ -4,22 +4,19 @@ import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.dataformat.avro.AvroAnnotationIntrospector; import com.fasterxml.jackson.dataformat.avro.AvroType; +import com.fasterxml.jackson.dataformat.avro.java8.deser.InstantDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.dataformat.avro.java8.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.dataformat.avro.java8.deser.OffsetDateTimeDeserializer; -import com.fasterxml.jackson.dataformat.avro.java8.deser.ZonedDateTimeDeserializer; +import com.fasterxml.jackson.dataformat.avro.java8.ser.InstantSerializer; import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateSerializer; import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.dataformat.avro.java8.ser.LocalTimeSerializer; -import com.fasterxml.jackson.dataformat.avro.java8.ser.OffsetDateTimeSerializer; -import com.fasterxml.jackson.dataformat.avro.java8.ser.ZonedDateTimeSerializer; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector { static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector(); @@ -33,22 +30,16 @@ public Object findSerializer(Annotated a) { if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { return LocalDateTimeSerializer.MILLIS; } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MILLIS; + if (a.getRawType().isAssignableFrom(Instant.class)) { + return InstantSerializer.MILLIS; } break; case TIMESTAMP_MICROSECOND: if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { return LocalDateTimeSerializer.MICROS; } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeSerializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeSerializer.MICROS; + if (a.getRawType().isAssignableFrom(Instant.class)) { + return InstantSerializer.MICROS; } break; case DATE: @@ -81,22 +72,16 @@ public Object findDeserializer(Annotated a) { if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { return LocalDateTimeDeserializer.MILLIS; } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MILLIS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MILLIS; + if (a.getRawType().isAssignableFrom(Instant.class)) { + return InstantDeserializer.MILLIS; } break; case TIMESTAMP_MICROSECOND: if (a.getRawType().isAssignableFrom(LocalDateTime.class)) { return LocalDateTimeDeserializer.MICROS; } - if (a.getRawType().isAssignableFrom(OffsetDateTime.class)) { - return OffsetDateTimeDeserializer.MICROS; - } - if (a.getRawType().isAssignableFrom(ZonedDateTime.class)) { - return ZonedDateTimeDeserializer.MICROS; + if (a.getRawType().isAssignableFrom(Instant.class)) { + return InstantDeserializer.MICROS; } break; case DATE: diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java index f32d22316..bb87ec8d0 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java @@ -7,10 +7,11 @@ import java.io.IOException; import java.time.Instant; import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { - final TimeUnit resolution; + private final TimeUnit resolution; final ZoneId zoneId = ZoneId.of("UTC"); BaseTimeJsonDeserializer(TimeUnit resolution) { @@ -22,20 +23,21 @@ public abstract class BaseTimeJsonDeserializer extends JsonDeserializer { @Override public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { final long input = p.getLongValue(); - final long output; + final ChronoUnit chronoUnit; + switch (this.resolution) { case MICROSECONDS: - output = TimeUnit.MICROSECONDS.toMillis(input); + chronoUnit = ChronoUnit.MICROS; break; case MILLISECONDS: - output = input; + chronoUnit = ChronoUnit.MILLIS; break; default: throw new UnsupportedOperationException( String.format("%s is not supported", this.resolution) ); } - final Instant instant = Instant.ofEpochMilli(output); + final Instant instant = Instant.EPOCH.plus(input, chronoUnit); return fromInstant(instant); } } diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/InstantDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/InstantDeserializer.java new file mode 100644 index 000000000..73285874e --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/InstantDeserializer.java @@ -0,0 +1,20 @@ +package com.fasterxml.jackson.dataformat.avro.java8.deser; + +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class InstantDeserializer extends BaseTimeJsonDeserializer { + public static JsonDeserializer MILLIS = new InstantDeserializer(TimeUnit.MILLISECONDS); + public static JsonDeserializer MICROS = new InstantDeserializer(TimeUnit.MICROSECONDS); + + InstantDeserializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant fromInstant(Instant input) { + return input; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java deleted file mode 100644 index 80b472b0c..000000000 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/OffsetDateTimeDeserializer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.deser; - -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.util.concurrent.TimeUnit; - -public class OffsetDateTimeDeserializer extends BaseTimeJsonDeserializer { - public static JsonDeserializer MILLIS = new OffsetDateTimeDeserializer(TimeUnit.MILLISECONDS); - public static JsonDeserializer MICROS = new OffsetDateTimeDeserializer(TimeUnit.MICROSECONDS); - - OffsetDateTimeDeserializer(TimeUnit resolution) { - super(resolution); - } - - @Override - protected OffsetDateTime fromInstant(Instant input) { - return OffsetDateTime.ofInstant(input, this.zoneId); - } -} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java deleted file mode 100644 index c1ae6eae6..000000000 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/ZonedDateTimeDeserializer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.deser; - -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.concurrent.TimeUnit; - -public class ZonedDateTimeDeserializer extends BaseTimeJsonDeserializer { - public static JsonDeserializer MILLIS = new ZonedDateTimeDeserializer(TimeUnit.MILLISECONDS); - public static JsonDeserializer MICROS = new ZonedDateTimeDeserializer(TimeUnit.MICROSECONDS); - - ZonedDateTimeDeserializer(TimeUnit resolution) { - super(resolution); - } - - @Override - protected ZonedDateTime fromInstant(Instant input) { - return ZonedDateTime.ofInstant(input, this.zoneId); - } -} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java index bd660531c..d388fa523 100644 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/BaseTimeJsonSerializer.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.time.Instant; import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; public abstract class BaseTimeJsonSerializer extends JsonSerializer { @@ -25,7 +26,7 @@ public void serialize(T input, JsonGenerator jsonGenerator, SerializerProvider s final long output; switch (this.resolution) { case MICROSECONDS: - output = TimeUnit.MILLISECONDS.toMicros(instant.toEpochMilli()); + output = ChronoUnit.MICROS.between(Instant.EPOCH, instant); break; case MILLISECONDS: output = instant.toEpochMilli(); diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/InstantSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/InstantSerializer.java new file mode 100644 index 000000000..eba3fc089 --- /dev/null +++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/InstantSerializer.java @@ -0,0 +1,20 @@ +package com.fasterxml.jackson.dataformat.avro.java8.ser; + +import com.fasterxml.jackson.databind.JsonSerializer; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class InstantSerializer extends BaseTimeJsonSerializer { + public static final JsonSerializer MILLIS = new InstantSerializer(TimeUnit.MILLISECONDS); + public static final JsonSerializer MICROS = new InstantSerializer(TimeUnit.MICROSECONDS); + + InstantSerializer(TimeUnit resolution) { + super(resolution); + } + + @Override + Instant toInstant(Instant input) { + return input; + } +} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java deleted file mode 100644 index 65f14147b..000000000 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/OffsetDateTimeSerializer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.ser; - -import com.fasterxml.jackson.databind.JsonSerializer; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.util.concurrent.TimeUnit; - -public class OffsetDateTimeSerializer extends BaseTimeJsonSerializer { - public static final JsonSerializer MILLIS = new OffsetDateTimeSerializer(TimeUnit.MILLISECONDS); - public static final JsonSerializer MICROS = new OffsetDateTimeSerializer(TimeUnit.MICROSECONDS); - - OffsetDateTimeSerializer(TimeUnit resolution) { - super(resolution); - } - - @Override - Instant toInstant(OffsetDateTime input) { - return input.toInstant(); - } -} diff --git a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java deleted file mode 100644 index 7cc61b697..000000000 --- a/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/ser/ZonedDateTimeSerializer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.ser; - -import com.fasterxml.jackson.databind.JsonSerializer; - -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.concurrent.TimeUnit; - -public class ZonedDateTimeSerializer extends BaseTimeJsonSerializer { - public static final JsonSerializer MILLIS = new ZonedDateTimeSerializer(TimeUnit.MILLISECONDS); - public static final JsonSerializer MICROS = new ZonedDateTimeSerializer(TimeUnit.MICROSECONDS); - - ZonedDateTimeSerializer(TimeUnit resolution) { - super(resolution); - } - - @Override - Instant toInstant(ZonedDateTime input) { - return input.toInstant(); - } -} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosInstantTest.java similarity index 68% rename from avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosInstantTest.java index 8910a743c..83aff00ac 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosInstantTest.java @@ -7,14 +7,12 @@ import org.apache.avro.Schema; import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.concurrent.TimeUnit; -public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase { - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); +public class TimestampMicrosInstantTest extends LogicalTypeTestCase { + static final Instant VALUE = new Date(1526955327123L).toInstant(); @Override protected Class dataClass() { @@ -40,17 +38,16 @@ protected TestCase testData() { @Override protected Object convertedValue() { - return 1526955327123L * 1000L; + return ChronoUnit.MICROS.between(Instant.EPOCH, VALUE); } - - static class TestCase extends TestData { + static class TestCase extends TestData { @JsonProperty(required = true) @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) - OffsetDateTime value; + Instant value; @Override - public OffsetDateTime value() { + public Instant value() { return this.value; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java deleted file mode 100644 index 0dccaf944..000000000 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroType; -import com.fasterxml.jackson.dataformat.avro.AvroMapper; -import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule; -import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; -import org.apache.avro.Schema; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -public class TimestampMicrosZonedDateTimeTest extends LogicalTypeTestCase { - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - - @Override - protected Class dataClass() { - return TestCase.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-micros"; - } - - @Override - protected TestCase testData() { - TestCase v = new TestCase(); - v.value = VALUE; - return v; - } - - @Override - protected Object convertedValue() { - return 1526955327123L * 1000L; - } - - @Override - protected void configure(AvroMapper mapper) { - mapper.registerModule(new AvroJavaTimeModule()); - } - - static class TestCase extends TestData { - @JsonProperty(required = true) - @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND) - ZonedDateTime value; - - @Override - public ZonedDateTime value() { - return this.value; - } - } - -} diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisInstantTest.java similarity index 72% rename from avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java rename to avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisInstantTest.java index db2e9ee8c..3d0e9aca7 100644 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java +++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisInstantTest.java @@ -1,21 +1,17 @@ package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroType; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; import org.apache.avro.Schema; import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; +import java.util.Date; -public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase { - static final OffsetDateTime VALUE = OffsetDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); +public class TimestampMillisInstantTest extends LogicalTypeTestCase { + static final Instant VALUE = new Date(1526955327123L).toInstant(); @Override protected Class dataClass() { @@ -49,13 +45,13 @@ protected void configure(AvroMapper mapper) { } - static class TestCase extends TestData { + static class TestCase extends TestData { @JsonProperty(required = true) @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) - OffsetDateTime value; + Instant value; @Override - public OffsetDateTime value() { + public Instant value() { return this.value; } } diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java deleted file mode 100644 index a3d4798e6..000000000 --- a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisZonedDateTimeTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.time; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.avro.AvroType; -import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.LogicalTypeTestCase; -import com.fasterxml.jackson.dataformat.avro.java8.logicaltypes.TestData; -import org.apache.avro.Schema; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -public class TimestampMillisZonedDateTimeTest extends LogicalTypeTestCase { - static final ZonedDateTime VALUE = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(1526955327123L), - ZoneId.of("UTC") - ); - - @Override - protected Class dataClass() { - return TestCase.class; - } - - @Override - protected Schema.Type schemaType() { - return Schema.Type.LONG; - } - - @Override - protected String logicalType() { - return "timestamp-millis"; - } - - @Override - protected TestCase testData() { - TestCase v = new TestCase(); - v.value = VALUE; - return v; - } - - @Override - protected Object convertedValue() { - return 1526955327123L; - } - - static class TestCase extends TestData { - @JsonProperty(required = true) - @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND) - ZonedDateTime value; - - @Override - public ZonedDateTime value() { - return this.value; - } - } - -}