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.awscore.exception; 17 18 import java.time.Duration; 19 import java.time.Instant; 20 import java.util.Optional; 21 import java.util.StringJoiner; 22 import software.amazon.awssdk.annotations.SdkPublicApi; 23 import software.amazon.awssdk.awscore.internal.AwsErrorCode; 24 import software.amazon.awssdk.awscore.internal.AwsStatusCode; 25 import software.amazon.awssdk.core.exception.SdkServiceException; 26 import software.amazon.awssdk.core.retry.ClockSkew; 27 import software.amazon.awssdk.http.SdkHttpResponse; 28 29 /** 30 * Extension of {@link SdkServiceException} that represents an error response returned 31 * by an Amazon web service. 32 * <p> 33 * <p> 34 * AmazonServiceException provides callers several pieces of 35 * information that can be used to obtain more information about the error and 36 * why it occurred. In particular, the errorType field can be used to determine 37 * if the caller's request was invalid, or the service encountered an error on 38 * the server side while processing it. 39 * 40 * @see SdkServiceException 41 */ 42 @SdkPublicApi 43 public class AwsServiceException extends SdkServiceException { 44 45 private AwsErrorDetails awsErrorDetails; 46 private Duration clockSkew; 47 AwsServiceException(Builder b)48 protected AwsServiceException(Builder b) { 49 super(b); 50 this.awsErrorDetails = b.awsErrorDetails(); 51 this.clockSkew = b.clockSkew(); 52 } 53 54 /** 55 * Additional details pertaining to an exception thrown by an AWS service. 56 * 57 * @return {@link AwsErrorDetails}. 58 */ awsErrorDetails()59 public AwsErrorDetails awsErrorDetails() { 60 return awsErrorDetails; 61 } 62 63 @Override getMessage()64 public String getMessage() { 65 if (awsErrorDetails != null) { 66 StringJoiner details = new StringJoiner(", ", "(", ")"); 67 details.add("Service: " + awsErrorDetails().serviceName()); 68 details.add("Status Code: " + statusCode()); 69 details.add("Request ID: " + requestId()); 70 if (extendedRequestId() != null) { 71 details.add("Extended Request ID: " + extendedRequestId()); 72 } 73 String message = super.getMessage(); 74 if (message == null) { 75 message = awsErrorDetails().errorMessage(); 76 } 77 return message + " " + details; 78 } 79 80 return super.getMessage(); 81 } 82 83 @Override isClockSkewException()84 public boolean isClockSkewException() { 85 if (super.isClockSkewException()) { 86 return true; 87 } 88 89 if (awsErrorDetails == null) { 90 return false; 91 } 92 93 if (AwsErrorCode.isDefiniteClockSkewErrorCode(awsErrorDetails.errorCode())) { 94 return true; 95 } 96 97 SdkHttpResponse sdkHttpResponse = awsErrorDetails.sdkHttpResponse(); 98 99 if (clockSkew == null || sdkHttpResponse == null) { 100 return false; 101 } 102 103 boolean isPossibleClockSkewError = AwsErrorCode.isPossibleClockSkewErrorCode(awsErrorDetails.errorCode()) || 104 AwsStatusCode.isPossibleClockSkewStatusCode(statusCode()); 105 106 return isPossibleClockSkewError && ClockSkew.isClockSkewed(Instant.now().minus(clockSkew), 107 ClockSkew.getServerTime(sdkHttpResponse).orElse(null)); 108 } 109 110 @Override isThrottlingException()111 public boolean isThrottlingException() { 112 return super.isThrottlingException() || 113 Optional.ofNullable(awsErrorDetails) 114 .map(a -> AwsErrorCode.isThrottlingErrorCode(a.errorCode())) 115 .orElse(false); 116 } 117 118 /** 119 * @return {@link Builder} instance to construct a new {@link AwsServiceException}. 120 */ builder()121 public static Builder builder() { 122 return new BuilderImpl(); 123 } 124 125 /** 126 * Create a {@link AwsServiceException.Builder} initialized with the properties of this {@code AwsServiceException}. 127 * 128 * @return A new builder initialized with this config's properties. 129 */ 130 @Override toBuilder()131 public Builder toBuilder() { 132 return new BuilderImpl(this); 133 } 134 serializableBuilderClass()135 public static Class<? extends Builder> serializableBuilderClass() { 136 return BuilderImpl.class; 137 } 138 139 public interface Builder extends SdkServiceException.Builder { 140 141 /** 142 * Specifies the additional awsErrorDetails from the service response. 143 * @param awsErrorDetails Object containing additional details from the response. 144 * @return This object for method chaining. 145 */ awsErrorDetails(AwsErrorDetails awsErrorDetails)146 Builder awsErrorDetails(AwsErrorDetails awsErrorDetails); 147 148 /** 149 * The {@link AwsErrorDetails} from the service response. 150 * 151 * @return {@link AwsErrorDetails}. 152 */ awsErrorDetails()153 AwsErrorDetails awsErrorDetails(); 154 155 /** 156 * The request-level time skew between the client and server date for the request that generated this exception. Positive 157 * values imply the client clock is "fast" and negative values imply the client clock is "slow". 158 */ clockSkew(Duration timeOffSet)159 Builder clockSkew(Duration timeOffSet); 160 161 /** 162 * The request-level time skew between the client and server date for the request that generated this exception. Positive 163 * values imply the client clock is "fast" and negative values imply the client clock is "slow". 164 */ clockSkew()165 Duration clockSkew(); 166 167 @Override message(String message)168 Builder message(String message); 169 170 @Override cause(Throwable t)171 Builder cause(Throwable t); 172 173 @Override requestId(String requestId)174 Builder requestId(String requestId); 175 176 @Override extendedRequestId(String extendedRequestId)177 Builder extendedRequestId(String extendedRequestId); 178 179 @Override statusCode(int statusCode)180 Builder statusCode(int statusCode); 181 182 @Override build()183 AwsServiceException build(); 184 } 185 186 protected static class BuilderImpl extends SdkServiceException.BuilderImpl implements Builder { 187 188 protected AwsErrorDetails awsErrorDetails; 189 private Duration clockSkew; 190 BuilderImpl()191 protected BuilderImpl() { 192 } 193 BuilderImpl(AwsServiceException ex)194 protected BuilderImpl(AwsServiceException ex) { 195 super(ex); 196 this.awsErrorDetails = ex.awsErrorDetails(); 197 } 198 199 @Override awsErrorDetails(AwsErrorDetails awsErrorDetails)200 public Builder awsErrorDetails(AwsErrorDetails awsErrorDetails) { 201 this.awsErrorDetails = awsErrorDetails; 202 return this; 203 } 204 205 @Override awsErrorDetails()206 public AwsErrorDetails awsErrorDetails() { 207 return awsErrorDetails; 208 } 209 getAwsErrorDetails()210 public AwsErrorDetails getAwsErrorDetails() { 211 return awsErrorDetails; 212 } 213 setAwsErrorDetails(AwsErrorDetails awsErrorDetails)214 public void setAwsErrorDetails(AwsErrorDetails awsErrorDetails) { 215 this.awsErrorDetails = awsErrorDetails; 216 } 217 218 @Override clockSkew(Duration clockSkew)219 public Builder clockSkew(Duration clockSkew) { 220 this.clockSkew = clockSkew; 221 return this; 222 } 223 224 @Override clockSkew()225 public Duration clockSkew() { 226 return clockSkew; 227 } 228 229 @Override message(String message)230 public Builder message(String message) { 231 this.message = message; 232 return this; 233 } 234 235 @Override cause(Throwable cause)236 public Builder cause(Throwable cause) { 237 this.cause = cause; 238 return this; 239 } 240 241 @Override writableStackTrace(Boolean writableStackTrace)242 public Builder writableStackTrace(Boolean writableStackTrace) { 243 this.writableStackTrace = writableStackTrace; 244 return this; 245 } 246 247 @Override requestId(String requestId)248 public Builder requestId(String requestId) { 249 this.requestId = requestId; 250 return this; 251 } 252 253 @Override extendedRequestId(String extendedRequestId)254 public Builder extendedRequestId(String extendedRequestId) { 255 this.extendedRequestId = extendedRequestId; 256 return this; 257 } 258 259 @Override statusCode(int statusCode)260 public Builder statusCode(int statusCode) { 261 this.statusCode = statusCode; 262 return this; 263 } 264 265 @Override build()266 public AwsServiceException build() { 267 return new AwsServiceException(this); 268 } 269 } 270 } 271