1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.protocols.core;
17
18 import java.time.Instant;
19 import java.util.Map;
20 import java.util.function.Function;
21 import software.amazon.awssdk.annotations.SdkProtectedApi;
22 import software.amazon.awssdk.core.SdkField;
23 import software.amazon.awssdk.core.exception.SdkClientException;
24 import software.amazon.awssdk.core.protocol.MarshallLocation;
25 import software.amazon.awssdk.core.traits.TimestampFormatTrait;
26 import software.amazon.awssdk.utils.DateUtils;
27
28 /**
29  * Implementation of {@link StringToValueConverter.StringToValue} that converts a string to an {@link Instant} type.
30  * Respects the {@link TimestampFormatTrait} if present.
31  */

32 @SdkProtectedApi
33 public final class StringToInstant implements StringToValueConverter.StringToValue<Instant> {
34
35     /**
36      * Default formats for the given location.
37      */

38     private final Map<MarshallLocation, TimestampFormatTrait.Format> defaultFormats;
39
40     private StringToInstant(Map<MarshallLocation, TimestampFormatTrait.Format> defaultFormats) {
41         this.defaultFormats = defaultFormats;
42     }
43
44     @Override
45     public Instant convert(String value, SdkField<Instant> field) {
46         if (value == null) {
47             return null;
48         }
49         TimestampFormatTrait.Format format = resolveTimestampFormat(field);
50         switch (format) {
51             case ISO_8601:
52                 return DateUtils.parseIso8601Date(value);
53             case UNIX_TIMESTAMP:
54                 return safeParseDate(DateUtils::parseUnixTimestampInstant).apply(value);
55             case UNIX_TIMESTAMP_MILLIS:
56                 return safeParseDate(DateUtils::parseUnixTimestampMillisInstant).apply(value);
57             case RFC_822:
58                 return DateUtils.parseRfc1123Date(value);
59             default:
60                 throw SdkClientException.create("Unrecognized timestamp format - " + format);
61         }
62     }
63
64     /**
65      * Wraps date unmarshalling function to handle the {@link NumberFormatException}.
66      * @param dateUnmarshaller Original date unmarshaller function.
67      * @return New date unmarshaller function with exception handling.
68      */

69     private Function<String, Instant> safeParseDate(Function<String, Instant> dateUnmarshaller) {
70         return value -> {
71             try {
72                 return dateUnmarshaller.apply(value);
73             } catch (NumberFormatException e) {
74                 throw SdkClientException.builder()
75                                         .message("Unable to parse date : " + value)
76                                         .cause(e)
77                                         .build();
78             }
79         };
80     }
81
82     private TimestampFormatTrait.Format resolveTimestampFormat(SdkField<Instant> field) {
83         TimestampFormatTrait trait = field.getTrait(TimestampFormatTrait.class);
84         if (trait == null) {
85             TimestampFormatTrait.Format format = defaultFormats.get(field.location());
86             if (format == null) {
87                 throw SdkClientException.create(
88                     String.format("Timestamps are not supported for this location (%s)", field.location()));
89             }
90             return format;
91         } else {
92             return trait.format();
93         }
94     }
95
96     /**
97      * @param defaultFormats Default formats for each {@link MarshallLocation} as defined by the protocol.
98      * @return New {@link StringToValueConverter.StringToValue} for {@link Instant} types.
99      */

100     public static StringToInstant create(Map<MarshallLocation, TimestampFormatTrait.Format> defaultFormats) {
101         return new StringToInstant(defaultFormats);
102     }
103 }
104