package io.swagger.configuration; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils; import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase; import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction; import com.fasterxml.jackson.datatype.threetenbp.function.Function; import org.threeten.bp.DateTimeException; import org.threeten.bp.DateTimeUtils; import org.threeten.bp.Instant; import org.threeten.bp.OffsetDateTime; import org.threeten.bp.ZoneId; import org.threeten.bp.ZonedDateTime; import org.threeten.bp.format.DateTimeFormatter; import org.threeten.bp.temporal.Temporal; import org.threeten.bp.temporal.TemporalAccessor; import java.io.IOException; import java.math.BigDecimal; /** * Deserializer for ThreeTen temporal {@link Instant}s, {@link OffsetDateTime}, * and {@link ZonedDateTime}s. * Adapted from the jackson threetenbp InstantDeserializer to add support for * deserializing rfc822 format. * * @author Nick Williams */ public class CustomInstantDeserializer extends ThreeTenDateTimeDeserializerBase { private static final long serialVersionUID = 1L; public static final CustomInstantDeserializer INSTANT = new CustomInstantDeserializer( Instant.class, DateTimeFormatter.ISO_INSTANT, new Function() { @Override public Instant apply(TemporalAccessor temporalAccessor) { return Instant.from(temporalAccessor); } }, new Function() { @Override public Instant apply(FromIntegerArguments a) { return Instant.ofEpochMilli(a.value); } }, new Function() { @Override public Instant apply(FromDecimalArguments a) { return Instant.ofEpochSecond(a.integer, a.fraction); } }, null); public static final CustomInstantDeserializer OFFSET_DATE_TIME = new CustomInstantDeserializer( OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, new Function() { @Override public OffsetDateTime apply(TemporalAccessor temporalAccessor) { return OffsetDateTime.from(temporalAccessor); } }, new Function() { @Override public OffsetDateTime apply(FromIntegerArguments a) { return OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); } }, new Function() { @Override public OffsetDateTime apply(FromDecimalArguments a) { return OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); } }, new BiFunction() { @Override public OffsetDateTime apply(OffsetDateTime d, ZoneId z) { return d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())); } }); public static final CustomInstantDeserializer ZONED_DATE_TIME = new CustomInstantDeserializer( ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, new Function() { @Override public ZonedDateTime apply(TemporalAccessor temporalAccessor) { return ZonedDateTime.from(temporalAccessor); } }, new Function() { @Override public ZonedDateTime apply(FromIntegerArguments a) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); } }, new Function() { @Override public ZonedDateTime apply(FromDecimalArguments a) { return ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); } }, new BiFunction() { @Override public ZonedDateTime apply(ZonedDateTime zonedDateTime, ZoneId zoneId) { return zonedDateTime.withZoneSameInstant(zoneId); } }); protected final Function fromMilliseconds; protected final Function fromNanoseconds; protected final Function parsedToValue; protected final BiFunction adjust; protected CustomInstantDeserializer(Class supportedType, DateTimeFormatter parser, Function parsedToValue, Function fromMilliseconds, Function fromNanoseconds, BiFunction adjust) { super(supportedType, parser); this.parsedToValue = parsedToValue; this.fromMilliseconds = fromMilliseconds; this.fromNanoseconds = fromNanoseconds; this.adjust = adjust == null ? new BiFunction() { @Override public T apply(T t, ZoneId zoneId) { return t; } } : adjust; } @SuppressWarnings("unchecked") protected CustomInstantDeserializer(CustomInstantDeserializer base, DateTimeFormatter f) { super((Class) base.handledType(), f); parsedToValue = base.parsedToValue; fromMilliseconds = base.fromMilliseconds; fromNanoseconds = base.fromNanoseconds; adjust = base.adjust; } @Override protected JsonDeserializer withDateFormat(DateTimeFormatter dtf) { if (dtf == _formatter) { return this; } return new CustomInstantDeserializer(this, dtf); } @SuppressWarnings("deprecation") @Override public T deserialize(JsonParser parser, DeserializationContext context) throws IOException { // NOTE: Timestamps contain no timezone info, and are always in configured TZ. // Only // string values have to be adjusted to the configured TZ. switch (parser.getCurrentTokenId()) { case JsonTokenId.ID_NUMBER_FLOAT: { BigDecimal value = parser.getDecimalValue(); long seconds = value.longValue(); int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds); return fromNanoseconds.apply(new FromDecimalArguments( seconds, nanoseconds, getZone(context))); } case JsonTokenId.ID_NUMBER_INT: { long timestamp = parser.getLongValue(); if (context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) { return this.fromNanoseconds.apply(new FromDecimalArguments( timestamp, 0, this.getZone(context))); } return this.fromMilliseconds.apply(new FromIntegerArguments( timestamp, this.getZone(context))); } case JsonTokenId.ID_STRING: { String string = parser.getText().trim(); if (string.length() == 0) { return null; } if (string.endsWith("+0000")) { string = string.substring(0, string.length() - 5) + "Z"; } T value; try { TemporalAccessor acc = _formatter.parse(string); value = parsedToValue.apply(acc); if (context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)) { return adjust.apply(value, this.getZone(context)); } } catch (DateTimeException e) { throw _peelDTE(e); } return value; } } throw context.mappingException("Expected type float, integer, or string."); } private ZoneId getZone(DeserializationContext context) { // Instants are always in UTC, so don't waste compute cycles return (_valueClass == Instant.class) ? null : DateTimeUtils.toZoneId(context.getTimeZone()); } private static class FromIntegerArguments { public final long value; public final ZoneId zoneId; private FromIntegerArguments(long value, ZoneId zoneId) { this.value = value; this.zoneId = zoneId; } } private static class FromDecimalArguments { public final long integer; public final int fraction; public final ZoneId zoneId; private FromDecimalArguments(long integer, int fraction, ZoneId zoneId) { this.integer = integer; this.fraction = fraction; this.zoneId = zoneId; } } }