|
18 | 18 |
|
19 | 19 | import java.io.IOException;
|
20 | 20 | import java.io.Reader;
|
| 21 | +import java.lang.reflect.Method; |
21 | 22 | import java.sql.SQLException;
|
22 | 23 | import java.sql.Timestamp;
|
23 | 24 | import java.time.Instant;
|
24 | 25 | import java.time.LocalDateTime;
|
25 | 26 | import java.time.ZoneId;
|
| 27 | +import java.time.ZonedDateTime; |
26 | 28 | import java.util.Arrays;
|
27 | 29 | import java.util.List;
|
28 | 30 | import java.util.Objects;
|
@@ -71,6 +73,10 @@ public class JacksonUndoLogParser implements UndoLogParser, Initialize {
|
71 | 73 |
|
72 | 74 | private static final Logger LOGGER = LoggerFactory.getLogger(JacksonUndoLogParser.class);
|
73 | 75 |
|
| 76 | + private static final String DM_JDBC_DRIVER_DMDB_TIMESTAMP = "dm.jdbc.driver.DmdbTimestamp"; |
| 77 | + |
| 78 | + private static final String VALUE_OF = "valueOf"; |
| 79 | + |
74 | 80 | /**
|
75 | 81 | * the zoneId for LocalDateTime
|
76 | 82 | */
|
@@ -120,6 +126,16 @@ public class JacksonUndoLogParser implements UndoLogParser, Initialize {
|
120 | 126 | */
|
121 | 127 | private final JsonDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer();
|
122 | 128 |
|
| 129 | + /** |
| 130 | + * customize serializer for dm.jdbc.driver.DmdbTimestamp |
| 131 | + */ |
| 132 | + private final JsonSerializer dmdbTimestampSerializer = new DmdbTimestampSerializer(); |
| 133 | + |
| 134 | + /** |
| 135 | + * customize deserializer for dm.jdbc.driver.DmdbTimestamp |
| 136 | + */ |
| 137 | + private final JsonDeserializer dmdbTimestampDeserializer = new DmdbTimestampDeserializer(); |
| 138 | + |
123 | 139 | @Override
|
124 | 140 | public void init() {
|
125 | 141 | try {
|
@@ -152,12 +168,25 @@ public void init() {
|
152 | 168 | module.addDeserializer(SerialClob.class, clobDeserializer);
|
153 | 169 | module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
|
154 | 170 | module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
|
| 171 | + registerDmdbTimestampModuleIfPresent(); |
155 | 172 | mapper.registerModule(module);
|
156 | 173 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
157 | 174 | mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
158 | 175 | mapper.enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
|
159 | 176 | }
|
160 | 177 |
|
| 178 | + private void registerDmdbTimestampModuleIfPresent() { |
| 179 | + try { |
| 180 | + Class<?> dmdbTimestampClass = Class.forName(DM_JDBC_DRIVER_DMDB_TIMESTAMP); |
| 181 | + module.addSerializer(dmdbTimestampClass, dmdbTimestampSerializer); |
| 182 | + module.addDeserializer(dmdbTimestampClass, dmdbTimestampDeserializer); |
| 183 | + } catch (ClassNotFoundException e) { |
| 184 | + // If the DmdbTimestamp class is not found, the serializers and deserializers will not be registered. |
| 185 | + // This is expected behavior since not all environments will have the dm.jdbc.driver.DmdbTimestamp class. |
| 186 | + // Therefore, no error log is recorded to avoid confusion for users without the dm driver. |
| 187 | + } |
| 188 | + } |
| 189 | + |
161 | 190 | @Override
|
162 | 191 | public String getName() {
|
163 | 192 | return NAME;
|
@@ -395,6 +424,98 @@ public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) thro
|
395 | 424 | }
|
396 | 425 | }
|
397 | 426 |
|
| 427 | + private static class DmdbTimestampSerializer extends JsonSerializer<Object> { |
| 428 | + |
| 429 | + private static final String TO_INSTANT = "toInstant"; |
| 430 | + private static final String GET_NANOS = "getNanos"; |
| 431 | + |
| 432 | + @Override |
| 433 | + public void serializeWithType(Object dmdbTimestamp, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { |
| 434 | + JsonToken valueShape = JsonToken.VALUE_NUMBER_INT; |
| 435 | + int nanos = getNanos(dmdbTimestamp); |
| 436 | + if (nanos % 1000000 > 0) { |
| 437 | + valueShape = JsonToken.START_ARRAY; |
| 438 | + } |
| 439 | + |
| 440 | + WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(dmdbTimestamp, valueShape)); |
| 441 | + serialize(dmdbTimestamp, gen, serializers); |
| 442 | + typeSer.writeTypeSuffix(gen, typeIdDef); |
| 443 | + } |
| 444 | + |
| 445 | + @Override |
| 446 | + public void serialize(Object dmdbTimestamp, JsonGenerator gen, SerializerProvider serializers) { |
| 447 | + try { |
| 448 | + Instant instant = getInstant(dmdbTimestamp); |
| 449 | + gen.writeNumber(instant.toEpochMilli()); |
| 450 | + // if has microseconds, serialized as an array, write the nano to the array |
| 451 | + int nanos = instant.getNano(); |
| 452 | + if (nanos % 1000000 > 0) { |
| 453 | + gen.writeNumber(nanos); |
| 454 | + } |
| 455 | + } catch (Exception e) { |
| 456 | + LOGGER.error("serialize dm.jdbc.driver.DmdbTimestamp error : {}", e.getMessage(), e); |
| 457 | + } |
| 458 | + } |
| 459 | + |
| 460 | + private int getNanos(Object dmdbTimestamp) throws IOException { |
| 461 | + try { |
| 462 | + Method getNanosMethod = dmdbTimestamp.getClass().getMethod(GET_NANOS); |
| 463 | + return (int) getNanosMethod.invoke(dmdbTimestamp); |
| 464 | + } catch (Exception e) { |
| 465 | + throw new IOException("Error getting nanos value from DmdbTimestamp", e); |
| 466 | + } |
| 467 | + } |
| 468 | + |
| 469 | + private Instant getInstant(Object dmdbTimestamp) throws IOException { |
| 470 | + try { |
| 471 | + Method toInstantMethod = dmdbTimestamp.getClass().getMethod(TO_INSTANT); |
| 472 | + return (Instant) toInstantMethod.invoke(dmdbTimestamp); |
| 473 | + } catch (Exception e) { |
| 474 | + throw new IOException("Error getting instant from DmdbTimestamp", e); |
| 475 | + } |
| 476 | + } |
| 477 | + } |
| 478 | + |
| 479 | + public class DmdbTimestampDeserializer extends JsonDeserializer<Object> { |
| 480 | + |
| 481 | + @Override |
| 482 | + public Object deserialize(JsonParser p, DeserializationContext ctxt) { |
| 483 | + try { |
| 484 | + Instant instant = parseInstant(p); |
| 485 | + return createDmdbTimestamp(instant); |
| 486 | + } catch (Exception e) { |
| 487 | + LOGGER.error("deserialize dm.jdbc.driver.DmdbTimestamp error : {}", e.getMessage(), e); |
| 488 | + } |
| 489 | + return null; |
| 490 | + } |
| 491 | + |
| 492 | + private Instant parseInstant(JsonParser p) throws IOException { |
| 493 | + try { |
| 494 | + if (p.isExpectedStartArrayToken()) { |
| 495 | + ArrayNode arrayNode = p.getCodec().readTree(p); |
| 496 | + long timestamp = arrayNode.get(0).asLong(); |
| 497 | + Instant instant = Instant.ofEpochMilli(timestamp); |
| 498 | + if (arrayNode.size() > 1) { |
| 499 | + int nano = arrayNode.get(1).asInt(); |
| 500 | + instant = instant.plusNanos(nano % 1000000); |
| 501 | + } |
| 502 | + return instant; |
| 503 | + } else { |
| 504 | + long timestamp = p.getLongValue(); |
| 505 | + return Instant.ofEpochMilli(timestamp); |
| 506 | + } |
| 507 | + } catch (IOException e) { |
| 508 | + throw new IOException("Error parsing Instant from JSON", e); |
| 509 | + } |
| 510 | + } |
| 511 | + |
| 512 | + private Object createDmdbTimestamp(Instant instant) throws Exception { |
| 513 | + Class<?> dmdbTimestampClass = Class.forName(DM_JDBC_DRIVER_DMDB_TIMESTAMP); |
| 514 | + Method valueOfMethod = dmdbTimestampClass.getMethod(VALUE_OF, ZonedDateTime.class); |
| 515 | + return valueOfMethod.invoke(null, instant.atZone(zoneId)); |
| 516 | + } |
| 517 | + } |
| 518 | + |
398 | 519 | /**
|
399 | 520 | * set zone id
|
400 | 521 | *
|
|
0 commit comments