diff --git a/avro-java8/pom.xml b/avro-java8/pom.xml
new file mode 100644
index 000000000..6afe15f0f
--- /dev/null
+++ b/avro-java8/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformats-binary
+ 2.10.0.pr2-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}
+
+
+
+
+ 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..ec0dd0532
--- /dev/null
+++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/AvroJavaTimeAnnotationIntrospector.java
@@ -0,0 +1,112 @@
+package com.fasterxml.jackson.dataformat.avro.java8;
+
+import com.fasterxml.jackson.core.Version;
+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.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 java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+class AvroJavaTimeAnnotationIntrospector extends AvroAnnotationIntrospector {
+ static final AvroJavaTimeAnnotationIntrospector INSTANCE = new AvroJavaTimeAnnotationIntrospector();
+
+ @Override
+ public Object findSerializer(Annotated a) {
+ 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(Instant.class)) {
+ return InstantSerializer.MILLIS;
+ }
+ break;
+ case TIMESTAMP_MICROSECOND:
+ if (a.getRawType().isAssignableFrom(LocalDateTime.class)) {
+ return LocalDateTimeSerializer.MICROS;
+ }
+ if (a.getRawType().isAssignableFrom(Instant.class)) {
+ return InstantSerializer.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) {
+ 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(Instant.class)) {
+ return InstantDeserializer.MILLIS;
+ }
+ break;
+ case TIMESTAMP_MICROSECOND:
+ if (a.getRawType().isAssignableFrom(LocalDateTime.class)) {
+ return LocalDateTimeDeserializer.MICROS;
+ }
+ if (a.getRawType().isAssignableFrom(Instant.class)) {
+ return InstantDeserializer.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;
+ }
+ }
+
+ 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..bb87ec8d0
--- /dev/null
+++ b/avro-java8/src/main/java/com/fasterxml/jackson/dataformat/avro/java8/deser/BaseTimeJsonDeserializer.java
@@ -0,0 +1,43 @@
+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.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+public abstract class BaseTimeJsonDeserializer extends JsonDeserializer {
+ private 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 ChronoUnit chronoUnit;
+
+ switch (this.resolution) {
+ case MICROSECONDS:
+ chronoUnit = ChronoUnit.MICROS;
+ break;
+ case MILLISECONDS:
+ chronoUnit = ChronoUnit.MILLIS;
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ String.format("%s is not supported", this.resolution)
+ );
+ }
+ 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/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..d388fa523
--- /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.time.temporal.ChronoUnit;
+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 = ChronoUnit.MICROS.between(Instant.EPOCH, instant);
+ 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/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/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-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
new file mode 100644
index 000000000..a9a92fca1
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/BytesDecimalTest.java
@@ -0,0 +1,50 @@
+package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.avro.AvroType;
+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)
+ @AvroType(precision = 3, scale = 3, schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL)
+ public BigDecimal value;
+
+ @Override
+ public BigDecimal value() {
+ return this.value;
+ }
+ }
+}
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
new file mode 100644
index 000000000..a8c23c926
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/FixedDecimalTest.java
@@ -0,0 +1,51 @@
+package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.avro.AvroType;
+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)
+ @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
+ public BigDecimal value() {
+ return this.value;
+ }
+ }
+
+}
diff --git a/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java
new file mode 100644
index 000000000..e6475a6bf
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/LogicalTypeTestCase.java
@@ -0,0 +1,101 @@
+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.AvroSchema;
+import com.fasterxml.jackson.dataformat.avro.java8.AvroJavaTimeModule;
+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(new AvroJavaTimeModule());
+ 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));
+ configure(this.mapper);
+ }
+
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ 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-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java
new file mode 100644
index 000000000..c01309015
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/TestData.java
@@ -0,0 +1,5 @@
+package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes;
+
+public abstract class TestData {
+ public abstract T value();
+}
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..6a54b0d90
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/UUIDTest.java
@@ -0,0 +1,49 @@
+package com.fasterxml.jackson.dataformat.avro.java8.logicaltypes;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.avro.AvroType;
+import org.apache.avro.Schema;
+
+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)
+ @AvroType(schemaType = Schema.Type.STRING, logicalType = AvroType.LogicalType.UUID)
+ 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/DateDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateDateTest.java
new file mode 100644
index 000000000..a490a65d0
--- /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.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;
+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)
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE)
+ 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/DateLocalDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.java
new file mode 100644
index 000000000..a0079c368
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/DateLocalDateTest.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.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;
+import org.apache.avro.Schema;
+
+import java.time.LocalDate;
+
+public class DateLocalDateTest 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 = VALUE;
+ return v;
+ }
+
+ @Override
+ protected Object convertedValue() {
+ return VALUE.toEpochDay();
+ }
+
+ @Override
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE)
+ 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/TimeMicrosDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosDateTest.java
new file mode 100644
index 000000000..a5cb4061f
--- /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.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;
+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)
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND)
+ 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/TimeMicrosLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.java
new file mode 100644
index 000000000..92a2cb4bf
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMicrosLocalTimeTest.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.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;
+import org.apache.avro.Schema;
+
+import java.time.LocalTime;
+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;
+ }
+
+ @Override
+ protected Schema.Type schemaType() {
+ return Schema.Type.LONG;
+ }
+
+ @Override
+ protected String logicalType() {
+ return "time-micros";
+ }
+
+ @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)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND)
+ 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/TimeMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisDateTest.java
new file mode 100644
index 000000000..db22f1419
--- /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.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;
+import org.apache.avro.Schema;
+
+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)
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND)
+
+ 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/TimeMillisLocalTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimeMillisLocalTimeTest.java
new file mode 100644
index 000000000..ec5fd4ac8
--- /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.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;
+import org.apache.avro.Schema;
+
+import java.time.LocalTime;
+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;
+ }
+
+ @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() {
+ return TimeUnit.NANOSECONDS.toMillis(VALUE.toNanoOfDay());
+ }
+
+ @Override
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND)
+ 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..c94f9aa25
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosDateTest.java
@@ -0,0 +1,53 @@
+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.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public class TimestampMicrosDateTest extends LogicalTypeTestCase {
+ static final Date VALUE = new Date(1526955327123L);
+
+ @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 TimeUnit.MILLISECONDS.toMicros(VALUE.getTime());
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND)
+ 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/TimestampMicrosInstantTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosInstantTest.java
new file mode 100644
index 000000000..83aff00ac
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosInstantTest.java
@@ -0,0 +1,55 @@
+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.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public class TimestampMicrosInstantTest extends LogicalTypeTestCase {
+ static final Instant VALUE = new Date(1526955327123L).toInstant();
+
+ @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 ChronoUnit.MICROS.between(Instant.EPOCH, VALUE);
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND)
+ Instant value;
+
+ @Override
+ public Instant 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..bdd4dd54d
--- /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.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.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+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;
+ }
+
+ @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() {
+ 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)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND)
+ 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..8910a743c
--- /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.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;
+
+public class TimestampMicrosOffsetDateTimeTest extends LogicalTypeTestCase {
+ static final OffsetDateTime VALUE = OffsetDateTime.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;
+ }
+
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND)
+ OffsetDateTime value;
+
+ @Override
+ public OffsetDateTime 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
new file mode 100644
index 000000000..0dccaf944
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMicrosZonedDateTimeTest.java
@@ -0,0 +1,64 @@
+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/TimestampMillisDateTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.java
new file mode 100644
index 000000000..efd80196f
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisDateTest.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.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;
+import org.apache.avro.Schema;
+
+import java.util.Date;
+
+public class TimestampMillisDateTest extends LogicalTypeTestCase {
+ static final Date VALUE = new Date(1526955327123L);
+
+ @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;
+ }
+
+ @Override
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND)
+ 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/TimestampMillisInstantTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisInstantTest.java
new file mode 100644
index 000000000..3d0e9aca7
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisInstantTest.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.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.util.Date;
+
+public class TimestampMillisInstantTest extends LogicalTypeTestCase {
+ static final Instant VALUE = new Date(1526955327123L).toInstant();
+
+ @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;
+ }
+
+ @Override
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND)
+ Instant value;
+
+ @Override
+ public Instant value() {
+ return this.value;
+ }
+ }
+
+}
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
new file mode 100644
index 000000000..0d44139e6
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisLocalDateTimeTest.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.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.LocalDateTime;
+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;
+ }
+
+ @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)
+
+ 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/TimestampMillisOffsetDateTimeTest.java b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.java
new file mode 100644
index 000000000..db2e9ee8c
--- /dev/null
+++ b/avro-java8/src/test/java/com/fasterxml/jackson/dataformat/avro/java8/logicaltypes/time/TimestampMillisOffsetDateTimeTest.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.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;
+import org.apache.avro.Schema;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+
+public class TimestampMillisOffsetDateTimeTest extends LogicalTypeTestCase {
+ static final OffsetDateTime VALUE = OffsetDateTime.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;
+ }
+
+ @Override
+ protected void configure(AvroMapper mapper) {
+
+ }
+
+ static class TestCase extends TestData {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND)
+ OffsetDateTime value;
+
+ @Override
+ 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..a3d4798e6
--- /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.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;
+ }
+ }
+
+}
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..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
@@ -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;
@@ -22,8 +16,35 @@
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;
import com.fasterxml.jackson.dataformat.avro.ser.CustomEncodingSerializer;
+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.math.BigDecimal;
+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:
@@ -31,8 +52,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 +62,235 @@
*
* @since 2.9
*/
-public class AvroAnnotationIntrospector extends AnnotationIntrospector
-{
- private static final long serialVersionUID = 1L;
+public class AvroAnnotationIntrospector extends AnnotationIntrospector {
+ private static final long serialVersionUID = 1L;
- public AvroAnnotationIntrospector() { }
+ public AvroAnnotationIntrospector() {
+ }
- @Override
- public Version version() {
- return PackageVersion.VERSION;
- }
+ @Override
+ public Version version() {
+ return PackageVersion.VERSION;
+ }
- @Override
- public boolean hasIgnoreMarker(AnnotatedMember m) {
- return _findAnnotation(m, AvroIgnore.class) != null;
- }
+ @Override
+ public boolean hasIgnoreMarker(AnnotatedMember m) {
+ return _findAnnotation(m, AvroIgnore.class) != null;
+ }
- @Override
- public PropertyName findNameForSerialization(Annotated a) {
- return _findName(a);
- }
+ @Override
+ public PropertyName findNameForSerialization(Annotated a) {
+ return _findName(a);
+ }
- @Override
- public PropertyName findNameForDeserialization(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 Object findDeserializer(Annotated a) {
+ AvroEncode ann = _findAnnotation(a, AvroEncode.class);
+ if (ann != null) {
+ return new CustomEncodingDeserializer<>((CustomEncoding>) ClassUtil.createInstance(ann.using(), true));
}
- @Override
- public String findPropertyDefaultValue(Annotated m) {
- AvroDefault ann = _findAnnotation(m, AvroDefault.class);
- return (ann == null) ? null : ann.value();
- }
+ AvroType logicalType = _findAnnotation(a, AvroType.class);
- @Override
- public List findPropertyAliases(Annotated m) {
- AvroAlias ann = _findAnnotation(m, AvroAlias.class);
- if (ann == null) {
- return null;
- }
- return Collections.singletonList(PropertyName.construct(ann.alias()));
+ 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;
+ }
- protected PropertyName _findName(Annotated a)
- {
- AvroName ann = _findAnnotation(a, AvroName.class);
- return (ann == null) ? null : PropertyName.construct(ann.value());
- }
+ @Override
+ public String findPropertyDefaultValue(Annotated m) {
+ AvroDefault ann = _findAnnotation(m, AvroDefault.class);
+ return (ann == null) ? null : ann.value();
+ }
- @Override
- public Boolean hasRequiredMarker(AnnotatedMember m) {
- if (_hasAnnotation(m, Nullable.class)) {
- return Boolean.FALSE;
- }
- return null;
+ @Override
+ public List findPropertyAliases(Annotated m) {
+ AvroAlias ann = _findAnnotation(m, AvroAlias.class);
+ if (ann == null) {
+ return null;
}
+ return Collections.singletonList(PropertyName.construct(ann.alias()));
+ }
- @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;
- }
+ protected PropertyName _findName(Annotated a) {
+ AvroName ann = _findAnnotation(a, AvroName.class);
+ return (ann == null) ? null : PropertyName.construct(ann.value());
+ }
- @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 Boolean hasRequiredMarker(AnnotatedMember m) {
+ if (_hasAnnotation(m, Nullable.class)) {
+ return Boolean.FALSE;
}
+ return null;
+ }
- @Override
- public List findSubtypes(Annotated a)
- {
- Class>[] types = _getUnionTypes(a);
- if (types == null) {
- 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;
}
- ArrayList names = new ArrayList<>(types.length);
- for (Class> subtype : types) {
- names.add(new NamedType(subtype, AvroSchemaHelper.getTypeId(subtype)));
- }
- return names;
+ }
}
+ return null;
+ }
- @Override
- public TypeResolverBuilder> findTypeResolver(MapperConfig> config, AnnotatedClass ac, JavaType baseType) {
- return _findTypeResolver(config, ac, baseType);
+ @Override
+ public Object findSerializer(Annotated a) {
+ if (a.hasAnnotation(Stringable.class)) {
+ return ToStringSerializer.class;
}
-
- @Override
- public TypeResolverBuilder> findPropertyTypeResolver(MapperConfig> config, AnnotatedMember am, JavaType baseType) {
- return _findTypeResolver(config, am, baseType);
+ AvroEncode ann = _findAnnotation(a, AvroEncode.class);
+ if (ann != null) {
+ return new CustomEncodingSerializer<>((CustomEncoding>) ClassUtil.createInstance(ann.using(), true));
}
+ 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;
+ }
- @Override
- public TypeResolverBuilder> findPropertyContentTypeResolver(MapperConfig> config, AnnotatedMember am, JavaType containerType) {
- return _findTypeResolver(config, am, containerType);
+ @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;
+ }
- 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;
+ @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;
+ 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/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/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/apacheimpl/ApacheAvroParserImpl.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/apacheimpl/ApacheAvroParserImpl.java
index 97af3aed4..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;
@@ -372,6 +374,32 @@ public int decodeEnum() throws IOException {
return (_enumIndex = _decoder.readEnum());
}
+ @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);
+ }
+
/*
/**********************************************************
/* Methods for AvroReadContext impls, other
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/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/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/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..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,26 +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
@@ -56,19 +55,32 @@ 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 ScalarDecoder.BytesDecimalReader(
+ decimal.getScale()
+ );
+ }
return READER_BYTES;
case DOUBLE:
return READER_DOUBLE;
case ENUM:
- return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols());
- case FIXED:
- return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type));
+ 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 ScalarDecoder.FixedDecimalReader(
+ decimal.getScale(),
+ type.getFixedSize()
+ );
+ }
+ 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:
@@ -77,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:
@@ -96,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/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/schema/RecordVisitor.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/RecordVisitor.java
index f1d6867bb..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,6 +5,8 @@
import java.util.List;
import java.util.Map;
+import com.fasterxml.jackson.dataformat.avro.AvroType;
+import org.apache.avro.LogicalTypes;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Type;
import org.apache.avro.reflect.AvroMeta;
@@ -36,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);
@@ -75,7 +77,7 @@ public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas
}
schemas.addSchema(type, _avroSchema);
}
-
+
@Override
public Schema builtAvroSchema() {
if (!_overridden) {
@@ -90,7 +92,7 @@ public Schema builtAvroSchema() {
/* JsonObjectFormatVisitor implementation
/**********************************************************
*/
-
+
@Override
public void property(BeanProperty writer) throws JsonMappingException
{
@@ -142,7 +144,7 @@ public void optionalProperty(String name, JsonFormatVisitable handler,
/* Internal methods
/**********************************************************************
*/
-
+
protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) throws JsonMappingException
{
Schema writerSchema;
@@ -152,31 +154,81 @@ 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 {
- JsonSerializer> ser = null;
+ AvroType type = prop.getAnnotation(AvroType.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
+ if(null != type) {
+ if(type.schemaType() == Type.FIXED) {
+ writerSchema = Schema.createFixed(type.typeName(),
+ null,
+ type.typeNamespace(),
+ type.fixedSize()
+ );
+ } else {
+ 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(writerSchema);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ String.format("%s is not a supported logical type.", type.logicalType())
+ );
}
- final SerializerProvider prov = getProvider();
- if (ser == null) {
- if (prov == null) {
- throw JsonMappingException.from(prov, "SerializerProvider missing for RecordVisitor");
+ } 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);
}
- 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();
}
/* 23-Nov-2012, tatu: Actually let's also assume that primitive type values
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/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);
+ }
+}
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);
+ }
+}
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/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/NonBSGenericDatumWriter.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java
index f7b87a99c..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
@@ -1,20 +1,21 @@
package com.fasterxml.jackson.dataformat.avro.ser;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-
+import org.apache.avro.Conversions;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Type;
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
@@ -25,7 +26,9 @@ public class NonBSGenericDatumWriter
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();
+
+
public NonBSGenericDatumWriter(Schema root) {
super(root);
}
@@ -57,6 +60,10 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio
case ENUM:
super.writeWithoutConversion(schema, GENERIC_DATA.createEnum(datum.toString(), schema), out);
return;
+ case FIXED:
+ case BYTES:
+ super.writeWithoutConversion(schema, datum, out);
+ return;
case INT:
if (datum.getClass() == CLS_STRING) {
String str = (String) datum;
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..faaac0ca9
--- /dev/null
+++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schema/TestLogicalTypes.java
@@ -0,0 +1,170 @@
+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.AvroType;
+import com.fasterxml.jackson.dataformat.avro.AvroMapper;
+import com.fasterxml.jackson.dataformat.avro.AvroSchema;
+import com.fasterxml.jackson.dataformat.avro.AvroTestBase;
+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 {
+
+ static class BytesDecimalType {
+ @JsonProperty(required = true)
+ @AvroType(schemaType = Schema.Type.BYTES, logicalType = AvroType.LogicalType.DECIMAL, precision = 5)
+ public BigDecimal value;
+ }
+
+ static class FixedNoNameDecimalType {
+ @JsonProperty(required = true)
+ @AvroType(precision = 5, schemaType = Schema.Type.FIXED, logicalType = AvroType.LogicalType.DECIMAL)
+ public BigDecimal value;
+ }
+
+ static class FixedDecimalType {
+ @JsonProperty(required = true)
+ @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 {
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MILLISECOND)
+ @JsonProperty(required = true)
+ public Date value;
+ }
+
+ static class TimeMillisecondsType {
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.TIME_MILLISECOND)
+ @JsonProperty(required = true)
+ public Date value;
+ }
+
+ static class TimestampMicrosecondsType {
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIMESTAMP_MICROSECOND)
+ @JsonProperty(required = true)
+ public Date value;
+ }
+
+ static class TimeMicrosecondsType {
+ @AvroType(schemaType = Schema.Type.LONG, logicalType = AvroType.LogicalType.TIME_MICROSECOND)
+ @JsonProperty(required = true)
+ public Date value;
+ }
+
+ static class DateType {
+ @AvroType(schemaType = Schema.Type.INT, logicalType = AvroType.LogicalType.DATE)
+ @JsonProperty(required = true)
+ public Date value;
+ }
+
+ static class UUIDType {
+ @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();
+ 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 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");
+ assertEquals(5, field.schema().getObjectProp("precision"));
+ 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();
+ 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");
+ }
+
+ 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/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
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