• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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