|
20 | 20 | import java.math.BigDecimal; |
21 | 21 | import java.math.BigInteger; |
22 | 22 | import java.nio.charset.StandardCharsets; |
| 23 | +import java.sql.Timestamp; |
23 | 24 | import java.text.DateFormat; |
24 | 25 | import java.text.ParseException; |
25 | 26 | import java.text.SimpleDateFormat; |
|
39 | 40 | import java.util.LinkedHashMap; |
40 | 41 | import java.util.List; |
41 | 42 | import java.util.Map; |
| 43 | +import java.util.Objects; |
42 | 44 | import java.util.TimeZone; |
43 | 45 | import java.util.UUID; |
| 46 | +import java.util.concurrent.CopyOnWriteArrayList; |
| 47 | +import java.util.function.Function; |
44 | 48 | import java.util.stream.Collectors; |
45 | 49 |
|
46 | 50 | import org.slf4j.Logger; |
|
60 | 64 | import graphql.language.Value; |
61 | 65 | import graphql.language.VariableReference; |
62 | 66 | import graphql.schema.Coercing; |
| 67 | +import graphql.schema.CoercingParseLiteralException; |
63 | 68 | import graphql.schema.CoercingParseValueException; |
64 | 69 | import graphql.schema.CoercingSerializeException; |
65 | 70 | import graphql.schema.GraphQLScalarType; |
@@ -577,45 +582,57 @@ private java.sql.Date parseStringToDate(String input) { |
577 | 582 | } |
578 | 583 | } |
579 | 584 |
|
580 | | - public static class GraphQLSqlTimestampCoercing implements Coercing<Object, Object> { |
| 585 | + public static class GraphQLSqlTimestampCoercing implements Coercing<Timestamp, Object> { |
581 | 586 |
|
582 | | - @Override |
583 | | - public Object serialize(Object input) { |
584 | | - if (input instanceof String) { |
585 | | - return parseStringToTimestamp((String) input); |
586 | | - } else if (input instanceof Date) { |
587 | | - return new java.sql.Timestamp(((Date) input).getTime()); |
588 | | - } else if (input instanceof Long) { |
589 | | - return new java.sql.Timestamp(((Long) input).longValue()); |
590 | | - } else if (input instanceof Integer) { |
591 | | - return new java.sql.Timestamp(((Integer) input).longValue()); |
| 587 | + private Timestamp doConvert(Object input) { |
| 588 | + if (input instanceof Long) { |
| 589 | + return new Timestamp(Long.class.cast(input)); |
| 590 | + } else if (input instanceof String) { |
| 591 | + Instant instant = DateTimeHelper.parseDate(String.class.cast(input)); |
| 592 | + |
| 593 | + return Timestamp.from(instant); |
| 594 | + } else if (input instanceof Timestamp) { |
| 595 | + return Timestamp.class.cast(input); |
592 | 596 | } |
| 597 | + |
593 | 598 | return null; |
594 | 599 | } |
595 | | - |
| 600 | + |
596 | 601 | @Override |
597 | | - public Object parseValue(Object input) { |
598 | | - return serialize(input); |
| 602 | + public Object serialize(Object input) { |
| 603 | + Timestamp result = doConvert(input); |
| 604 | + |
| 605 | + if (result == null) { |
| 606 | + throw new CoercingSerializeException("Invalid value '" + input + "' for Timestamp"); |
| 607 | + } |
| 608 | + |
| 609 | + return DateTimeFormatter.ISO_INSTANT.format(result.toInstant()); |
599 | 610 | } |
600 | 611 |
|
601 | 612 | @Override |
602 | | - public Object parseLiteral(Object input) { |
603 | | - if (input instanceof StringValue) { |
604 | | - return parseStringToTimestamp(((StringValue) input).getValue()); |
605 | | - } else if (input instanceof IntValue) { |
606 | | - BigInteger value = ((IntValue) input).getValue(); |
607 | | - return new java.sql.Date(value.longValue()); |
| 613 | + public Timestamp parseValue(Object input) { |
| 614 | + Timestamp result = doConvert(input); |
| 615 | + |
| 616 | + if (result == null) { |
| 617 | + throw new CoercingParseValueException("Invalid value '" + input + "' for Timestamp"); |
608 | 618 | } |
609 | | - return null; |
| 619 | + return result; |
610 | 620 | } |
611 | 621 |
|
612 | | - private java.sql.Timestamp parseStringToTimestamp(String input) { |
613 | | - try { |
614 | | - return new java.sql.Timestamp(DateFormat.getInstance().parse(input).getTime()); |
615 | | - } catch (ParseException e) { |
616 | | - log.warn("Failed to parse Timestamp from input: " + input, e); |
617 | | - return null; |
| 622 | + @Override |
| 623 | + public Timestamp parseLiteral(Object input) { |
| 624 | + Object value = null; |
| 625 | + |
| 626 | + if (IntValue.class.isInstance(input)) { |
| 627 | + value = IntValue.class.cast(input).getValue().longValue(); |
| 628 | + } |
| 629 | + else if (StringValue.class.isInstance(input)) { |
| 630 | + value = StringValue.class.cast(input).getValue(); |
| 631 | + } else { |
| 632 | + throw new CoercingParseLiteralException("Invalid value '" + input + "' for Timestamp"); |
618 | 633 | } |
| 634 | + |
| 635 | + return doConvert(value); |
619 | 636 | } |
620 | 637 | } |
621 | 638 |
|
@@ -724,5 +741,88 @@ public Object parseLiteral(Object value, Map<String, Object> variables) { |
724 | 741 | } |
725 | 742 |
|
726 | 743 | } |
| 744 | + |
| 745 | + public final static class DateTimeHelper { |
| 746 | + |
| 747 | + static final List<Function<String, Instant>> CONVERTERS = new CopyOnWriteArrayList<>(); |
| 748 | + |
| 749 | + static { |
| 750 | + CONVERTERS.add(new InstantConverter()); |
| 751 | + CONVERTERS.add(new OffsetDateTimeConverter()); |
| 752 | + CONVERTERS.add(new ZonedDateTimeConverter()); |
| 753 | + CONVERTERS.add(new LocalDateTimeConverter()); |
| 754 | + CONVERTERS.add(new LocalDateConverter()); |
| 755 | + } |
| 756 | + |
| 757 | + public static Instant parseDate(String date) { |
| 758 | + Objects.requireNonNull(date, "date"); |
| 759 | + |
| 760 | + for (Function<String, Instant> converter : CONVERTERS) { |
| 761 | + try { |
| 762 | + return converter.apply(date); |
| 763 | + } catch (java.time.format.DateTimeParseException ignored) { |
| 764 | + } |
| 765 | + } |
| 766 | + |
| 767 | + return null; |
| 768 | + } |
| 769 | + |
| 770 | + static class InstantConverter implements Function<String, Instant> { |
| 771 | + |
| 772 | + @Override |
| 773 | + public Instant apply(String date) { |
| 774 | + return Instant.parse(date); |
| 775 | + } |
| 776 | + } |
| 777 | + |
| 778 | + static class OffsetDateTimeConverter implements Function<String, Instant> { |
| 779 | + |
| 780 | + static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneOffset.UTC); |
| 781 | + |
| 782 | + @Override |
| 783 | + public Instant apply(String date) { |
| 784 | + return OffsetDateTime.parse(date, FORMATTER) |
| 785 | + .toInstant(); |
| 786 | + } |
| 787 | + } |
| 788 | + |
| 789 | + static class ZonedDateTimeConverter implements Function<String, Instant> { |
| 790 | + |
| 791 | + static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneOffset.UTC); |
727 | 792 |
|
| 793 | + @Override |
| 794 | + public Instant apply(String date) { |
| 795 | + return ZonedDateTime.parse(date, FORMATTER) |
| 796 | + .toInstant(); |
| 797 | + } |
| 798 | + } |
| 799 | + |
| 800 | + |
| 801 | + static class LocalDateTimeConverter implements Function<String, Instant> { |
| 802 | + |
| 803 | + static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneOffset.UTC); |
| 804 | + |
| 805 | + @Override |
| 806 | + public Instant apply(String date) { |
| 807 | + return LocalDateTime.parse(date, FORMATTER) |
| 808 | + .toInstant(ZoneOffset.UTC); |
| 809 | + } |
| 810 | + } |
| 811 | + |
| 812 | + static class LocalDateConverter implements Function<String, Instant> { |
| 813 | + |
| 814 | + static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneOffset.UTC); |
| 815 | + |
| 816 | + @Override |
| 817 | + public Instant apply(String date) { |
| 818 | + LocalDate localDate = LocalDate.parse(date, FORMATTER); |
| 819 | + |
| 820 | + return localDate.atStartOfDay() |
| 821 | + .toInstant(ZoneOffset.UTC); |
| 822 | + } |
| 823 | + } |
| 824 | + } |
| 825 | + |
| 826 | + |
| 827 | + |
728 | 828 | } |
0 commit comments