1 /* 2 * Copyright 2015 Google LLC 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 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.cloud; 18 19 import com.google.api.core.InternalApi; 20 import com.google.common.base.MoreObjects; 21 import java.io.IOException; 22 import java.io.Serializable; 23 import java.net.SocketException; 24 import java.net.SocketTimeoutException; 25 import java.security.cert.CertificateException; 26 import java.util.Objects; 27 import java.util.Set; 28 import java.util.concurrent.ExecutionException; 29 import javax.net.ssl.SSLException; 30 import javax.net.ssl.SSLHandshakeException; 31 32 /** Base class for all service exceptions. */ 33 public class BaseServiceException extends RuntimeException { 34 35 private static final long serialVersionUID = 759921776378760835L; 36 public static final int UNKNOWN_CODE = 0; 37 38 private final int code; 39 private final boolean retryable; 40 private final String reason; 41 private final String location; 42 private final String debugInfo; 43 44 @InternalApi 45 public static final class ExceptionData implements Serializable { 46 private static final long serialVersionUID = 2222230861338426753L; 47 48 private final String message; 49 private final Throwable cause; 50 private final int code; 51 private final boolean retryable; 52 private final String reason; 53 private final String location; 54 private final String debugInfo; 55 ExceptionData( String message, Throwable cause, int code, boolean retryable, String reason, String location, String debugInfo)56 private ExceptionData( 57 String message, 58 Throwable cause, 59 int code, 60 boolean retryable, 61 String reason, 62 String location, 63 String debugInfo) { 64 this.message = message; 65 this.cause = cause; 66 this.code = code; 67 this.retryable = retryable; 68 this.reason = reason; 69 this.location = location; 70 this.debugInfo = debugInfo; 71 } 72 getMessage()73 public String getMessage() { 74 return message; 75 } 76 getCause()77 public Throwable getCause() { 78 return cause; 79 } 80 getCode()81 public int getCode() { 82 return code; 83 } 84 isRetryable()85 public boolean isRetryable() { 86 return retryable; 87 } 88 getReason()89 public String getReason() { 90 return reason; 91 } 92 getLocation()93 public String getLocation() { 94 return location; 95 } 96 getDebugInfo()97 public String getDebugInfo() { 98 return debugInfo; 99 } 100 newBuilder()101 public static Builder newBuilder() { 102 return new Builder(); 103 } 104 from(int code, String message, String reason, boolean retryable)105 public static ExceptionData from(int code, String message, String reason, boolean retryable) { 106 return from(code, message, reason, retryable, null); 107 } 108 from( int code, String message, String reason, boolean retryable, Throwable cause)109 public static ExceptionData from( 110 int code, String message, String reason, boolean retryable, Throwable cause) { 111 return newBuilder() 112 .setCode(code) 113 .setMessage(message) 114 .setReason(reason) 115 .setRetryable(retryable) 116 .setCause(cause) 117 .build(); 118 } 119 120 @InternalApi 121 public static final class Builder { 122 123 private String message; 124 private Throwable cause; 125 private int code; 126 private boolean retryable; 127 private String reason; 128 private String location; 129 private String debugInfo; 130 Builder()131 private Builder() {} 132 setMessage(String message)133 public Builder setMessage(String message) { 134 this.message = message; 135 return this; 136 } 137 setCause(Throwable cause)138 public Builder setCause(Throwable cause) { 139 this.cause = cause; 140 return this; 141 } 142 setCode(int code)143 public Builder setCode(int code) { 144 this.code = code; 145 return this; 146 } 147 setRetryable(boolean retryable)148 public Builder setRetryable(boolean retryable) { 149 this.retryable = retryable; 150 return this; 151 } 152 setReason(String reason)153 public Builder setReason(String reason) { 154 this.reason = reason; 155 return this; 156 } 157 setLocation(String location)158 public Builder setLocation(String location) { 159 this.location = location; 160 return this; 161 } 162 setDebugInfo(String debugInfo)163 public Builder setDebugInfo(String debugInfo) { 164 this.debugInfo = debugInfo; 165 return this; 166 } 167 build()168 public ExceptionData build() { 169 return new ExceptionData(message, cause, code, retryable, reason, location, debugInfo); 170 } 171 } 172 } 173 174 @InternalApi 175 public static final class Error implements Serializable { 176 177 private static final long serialVersionUID = -4019600198652965721L; 178 179 private final Integer code; 180 private final String reason; 181 private final boolean rejected; 182 Error(Integer code, String reason)183 public Error(Integer code, String reason) { 184 this(code, reason, false); 185 } 186 Error(Integer code, String reason, boolean rejected)187 public Error(Integer code, String reason, boolean rejected) { 188 this.code = code; 189 this.reason = reason; 190 this.rejected = rejected; 191 } 192 193 /** Returns the code associated with this exception. */ getCode()194 public Integer getCode() { 195 return code; 196 } 197 198 /** 199 * Returns true if the error indicates that the API call was certainly not accepted by the 200 * server. For instance, if the server returns a rate limit exceeded error, it certainly did not 201 * process the request and this method will return {@code true}. 202 */ isRejected()203 public boolean isRejected() { 204 return rejected; 205 } 206 207 /** Returns the reason that caused the exception. */ getReason()208 public String getReason() { 209 return reason; 210 } 211 212 @InternalApi isRetryable(boolean idempotent, Set<Error> retryableErrors)213 public boolean isRetryable(boolean idempotent, Set<Error> retryableErrors) { 214 return BaseServiceException.isRetryable(code, reason, idempotent, retryableErrors); 215 } 216 217 @Override toString()218 public String toString() { 219 return MoreObjects.toStringHelper(this) 220 .add("code", code) 221 .add("reason", reason) 222 .add("rejected", rejected) 223 .toString(); 224 } 225 226 @Override hashCode()227 public int hashCode() { 228 return Objects.hash(code, reason, rejected); 229 } 230 231 @Override equals(Object obj)232 public boolean equals(Object obj) { 233 if (!(obj instanceof Error)) { 234 return false; 235 } 236 Error o = (Error) obj; 237 return Objects.equals(code, o.code) 238 && Objects.equals(reason, o.reason) 239 && Objects.equals(rejected, o.rejected); 240 } 241 } 242 243 @InternalApi isRetryable( Integer code, String reason, boolean idempotent, Set<Error> retryableErrors)244 public static boolean isRetryable( 245 Integer code, String reason, boolean idempotent, Set<Error> retryableErrors) { 246 for (Error retryableError : retryableErrors) { 247 if ((retryableError.getCode() == null || retryableError.getCode().equals(code)) 248 && (retryableError.getReason() == null || retryableError.getReason().equals(reason))) { 249 return idempotent || retryableError.isRejected(); 250 } 251 } 252 return false; 253 } 254 255 @InternalApi isRetryable(boolean idempotent, IOException exception)256 public static boolean isRetryable(boolean idempotent, IOException exception) { 257 boolean exceptionIsRetryable = 258 exception instanceof SocketTimeoutException 259 || exception instanceof SocketException 260 || (exception instanceof SSLException 261 && exception.getMessage().contains("Connection has been shutdown: ")) 262 || (exception instanceof SSLHandshakeException 263 && !(exception.getCause() instanceof CertificateException)) 264 || "insufficient data written".equals(exception.getMessage()) 265 || "Error writing request body to server".equals(exception.getMessage()); 266 return idempotent && exceptionIsRetryable; 267 } 268 269 @InternalApi translate(RetryHelper.RetryHelperException ex)270 public static void translate(RetryHelper.RetryHelperException ex) { 271 if (ex.getCause() instanceof BaseServiceException) { 272 throw (BaseServiceException) ex.getCause(); 273 } 274 } 275 276 @InternalApi translate(ExecutionException ex)277 public static void translate(ExecutionException ex) { 278 if (ex.getCause() instanceof BaseServiceException) { 279 throw (BaseServiceException) ex.getCause(); 280 } 281 } 282 283 @InternalApi("This class should only be extended within google-cloud-java") BaseServiceException(ExceptionData exceptionData)284 protected BaseServiceException(ExceptionData exceptionData) { 285 super(exceptionData.getMessage(), exceptionData.getCause()); 286 this.code = exceptionData.getCode(); 287 this.reason = exceptionData.getReason(); 288 this.retryable = exceptionData.isRetryable(); 289 this.location = exceptionData.getLocation(); 290 this.debugInfo = exceptionData.getDebugInfo(); 291 } 292 293 /** Returns the code associated with this exception. */ getCode()294 public int getCode() { 295 return code; 296 } 297 298 /** Returns the reason that caused the exception. */ getReason()299 public String getReason() { 300 return reason; 301 } 302 303 /** Returns {@code true} when it is safe to retry the operation that caused this exception. */ isRetryable()304 public boolean isRetryable() { 305 return retryable; 306 } 307 308 /** 309 * Returns the service location where the error causing the exception occurred. Returns {@code 310 * null} if not available. 311 */ getLocation()312 public String getLocation() { 313 return location; 314 } 315 316 @InternalApi getDebugInfo()317 public String getDebugInfo() { 318 return debugInfo; 319 } 320 321 @Override equals(Object obj)322 public boolean equals(Object obj) { 323 if (obj == this) { 324 return true; 325 } 326 if (!(obj instanceof BaseServiceException)) { 327 return false; 328 } 329 BaseServiceException other = (BaseServiceException) obj; 330 return Objects.equals(getCause(), other.getCause()) 331 && Objects.equals(getMessage(), other.getMessage()) 332 && code == other.code 333 && retryable == other.retryable 334 && Objects.equals(reason, other.reason) 335 && Objects.equals(location, other.location) 336 && Objects.equals(debugInfo, other.debugInfo); 337 } 338 339 @Override hashCode()340 public int hashCode() { 341 return Objects.hash(getCause(), getMessage(), code, retryable, reason, location, debugInfo); 342 } 343 } 344