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.core.retry;
17
18 import java.util.Optional;
19 import java.util.function.Supplier;
20 import software.amazon.awssdk.annotations.SdkPublicApi;
21 import software.amazon.awssdk.core.SdkSystemSetting;
22 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
23 import software.amazon.awssdk.core.retry.conditions.TokenBucketRetryCondition;
24 import software.amazon.awssdk.profiles.ProfileFile;
25 import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
26 import software.amazon.awssdk.profiles.ProfileProperty;
27 import software.amazon.awssdk.utils.OptionalUtils;
28 import software.amazon.awssdk.utils.StringUtils;
29
30 /**
31  * A retry mode is a collection of retry behaviors encoded under a single value. For example, the {@link #LEGACY} retry mode will
32  * retry up to three times, and the {@link #STANDARD} will retry up to two times.
33  *
34  * <p>
35  * While the {@link #LEGACY} retry mode is specific to Java, the {@link #STANDARD} retry mode is standardized across all of the
36  * AWS SDKs.
37  *
38  * <p>
39  * The retry mode can be configured:
40  * <ol>
41  *     <li>Directly on a client via {@link ClientOverrideConfiguration.Builder#retryPolicy(RetryMode)}.</li>
42  *     <li>Directly on a client via a combination of {@link RetryPolicy#builder(RetryMode)} or
43  *     {@link RetryPolicy#forRetryMode(RetryMode)}, and {@link ClientOverrideConfiguration.Builder#retryPolicy(RetryPolicy)}</li>
44  *     <li>On a configuration profile via the "retry_mode" profile file property.</li>
45  *     <li>Globally via the "aws.retryMode" system property.</li>
46  *     <li>Globally via the "AWS_RETRY_MODE" environment variable.</li>
47  * </ol>
48  */

49 @SdkPublicApi
50 public enum RetryMode {
51     /**
52      * The LEGACY retry mode, specific to the Java SDK, and characterized by:
53      * <ol>
54      *     <li>Up to 3 retries, or more for services like DynamoDB (which has up to 8).</li>
55      *     <li>Zero token are subtracted from the {@link TokenBucketRetryCondition} when throttling exceptions are encountered.
56      *     </li>
57      * </ol>
58      *
59      * <p>
60      * This is the retry mode that is used when no other mode is configured.
61      */

62     LEGACY,
63
64
65     /**
66      * The STANDARD retry mode, shared by all AWS SDK implementations, and characterized by:
67      * <ol>
68      *     <li>Up to 2 retries, regardless of service.</li>
69      *     <li>Throttling exceptions are treated the same as other exceptions for the purposes of the
70      *     {@link TokenBucketRetryCondition}.</li>
71      * </ol>
72      */

73     STANDARD;
74
75     /**
76      * Retrieve the default retry mode by consulting the locations described in {@link RetryMode}, or LEGACY if no value is
77      * configured.
78      */

79     public static RetryMode defaultRetryMode() {
80         return resolver().resolve();
81     }
82
83     /**
84      * Create a {@link Resolver} that allows customizing the variables used during determination of a {@link RetryMode}.
85      */

86     public static Resolver resolver() {
87         return new Resolver();
88     }
89
90     /**
91      * Allows customizing the variables used during determination of a {@link RetryMode}. Created via {@link #resolver()}.
92      */

93     public static class Resolver {
94         private Supplier<ProfileFile> profileFile;
95         private String profileName;
96
97         private Resolver() {
98         }
99
100         /**
101          * Configure the profile file that should be used when determining the {@link RetryMode}. The supplier is only consulted
102          * if a higher-priority determinant (e.g. environment variables) does not find the setting.
103          */

104         public Resolver profileFile(Supplier<ProfileFile> profileFile) {
105             this.profileFile = profileFile;
106             return this;
107         }
108
109         /**
110          * Configure the profile file name should be used when determining the {@link RetryMode}.
111          */

112         public Resolver profileName(String profileName) {
113             this.profileName = profileName;
114             return this;
115         }
116
117         /**
118          * Resolve which retry mode should be used, based on the configured values.
119          */

120         public RetryMode resolve() {
121             return OptionalUtils.firstPresent(Resolver.fromSystemSettings(), () -> fromProfileFile(profileFile, profileName))
122                                 .orElse(RetryMode.LEGACY);
123         }
124
125         private static Optional<RetryMode> fromSystemSettings() {
126             return SdkSystemSetting.AWS_RETRY_MODE.getStringValue()
127                                                   .flatMap(Resolver::fromString);
128         }
129
130         private static Optional<RetryMode> fromProfileFile(Supplier<ProfileFile> profileFile, String profileName) {
131             profileFile = profileFile != null ? profileFile : ProfileFile::defaultProfileFile;
132             profileName = profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
133             return profileFile.get()
134                               .profile(profileName)
135                               .flatMap(p -> p.property(ProfileProperty.RETRY_MODE))
136                               .flatMap(Resolver::fromString);
137         }
138
139         private static Optional<RetryMode> fromString(String string) {
140             if (string == null || string.isEmpty()) {
141                 return Optional.empty();
142             }
143
144             switch (StringUtils.lowerCase(string)) {
145                 case "legacy":
146                     return Optional.of(LEGACY);
147                 case "standard":
148                     return Optional.of(STANDARD);
149                 default:
150                     throw new IllegalStateException("Unsupported retry policy mode configured: " + string);
151             }
152         }
153     }
154 }
155