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