• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 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 package com.google.android.libraries.mobiledatadownload.downloader.offroad;
17 
18 import com.google.android.downloader.RequestException;
19 import com.google.android.libraries.mobiledatadownload.DownloadException;
20 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
21 
22 /**
23  * Handles mapping exceptions from Downloader2 into the equivalent MDD {@link DownloadException}.
24  *
25  * <p>Common exceptions are parsed and handled by default, but the underlying network stack may
26  * include special Exceptions and/or error codes that need to be parsed. If this is the case, a
27  * {@link NetworkStackExceptionHandler} can be provided to perform this parsing.
28  */
29 public final class ExceptionHandler {
30   /**
31    * The maximum amount of attempts we recurse before stopping in {@link
32    * #mapExceptionToDownloadResultCode}.
33    */
34   private static final int EXCEPTION_TO_CODE_RECURSION_LIMIT = 5;
35 
36   /** The handler of underlying network stack failures. */
37   private final NetworkStackExceptionHandler internalExceptionHandler;
38 
ExceptionHandler(NetworkStackExceptionHandler internalExceptionHandler)39   private ExceptionHandler(NetworkStackExceptionHandler internalExceptionHandler) {
40     this.internalExceptionHandler = internalExceptionHandler;
41   }
42 
43   /** Convenience method to return a new ExceptionHandler with default handling. */
withDefaultHandling()44   public static ExceptionHandler withDefaultHandling() {
45     return new ExceptionHandler(new NetworkStackExceptionHandler() {});
46   }
47 
48   /** Return a new instance with specific handling for a network stack. */
withNetworkStackHandling( NetworkStackExceptionHandler internalExceptionHandler)49   public static ExceptionHandler withNetworkStackHandling(
50       NetworkStackExceptionHandler internalExceptionHandler) {
51     return new ExceptionHandler(internalExceptionHandler);
52   }
53 
54   /**
55    * Map given failure to a {@link DownloadException}.
56    *
57    * <p>For most cases, this method does not need to be overridden.
58    *
59    * <p><em>NOTE:</em> If the given throwable is already a {@link DownloadException}, it is returned
60    * immediately. In this case, the given message will <b>not</b> be used (the message from the
61    * given throwable will be used instead).
62    *
63    * @param message top-level message that should be used for the returned {@link DownloadException}
64    * @param throwable generic throwable that should be mapped to {@link DownloadException}
65    * @return {@link DownloadException} that wraps around given throwable with appropriate {@link
66    *     DownloadResultCode}
67    */
mapToDownloadException(String message, Throwable throwable)68   public DownloadException mapToDownloadException(String message, Throwable throwable) {
69     if (throwable instanceof DownloadException) {
70       // Exception is already an MDD DownloadException -- return it.
71       return (DownloadException) throwable;
72     }
73 
74     DownloadResultCode code = mapExceptionToDownloadResultCode(throwable, /* iteration= */ 0);
75 
76     return DownloadException.builder()
77         .setMessage(message)
78         .setDownloadResultCode(code)
79         .setCause(throwable)
80         .build();
81   }
82 
83   /**
84    * Map exception to {@link DownloadResultCode}.
85    *
86    * @param throwable the exception to map to a {@link DownloadResultCode}
87    */
mapExceptionToDownloadResultCode(Throwable throwable, int iteration)88   private DownloadResultCode mapExceptionToDownloadResultCode(Throwable throwable, int iteration) {
89     // Check recursion limit and return unknown error if it is hit.
90     if (iteration >= EXCEPTION_TO_CODE_RECURSION_LIMIT) {
91       return DownloadResultCode.UNKNOWN_ERROR;
92     }
93 
94     DownloadResultCode networkStackMapperResult =
95         internalExceptionHandler.mapFromNetworkStackException(throwable);
96     if (!networkStackMapperResult.equals(DownloadResultCode.UNKNOWN_ERROR)) {
97       // network stack mapper returned known result code -- return it instead of performing common
98       // mapping.
99       return networkStackMapperResult;
100     }
101 
102     if (throwable instanceof DownloadException) {
103       // exception in the chain is already an MDD DownloadException -- use its code
104       return ((DownloadException) throwable).getDownloadResultCode();
105     }
106 
107     if (throwable instanceof RequestException) {
108       // Check error details for http status code error.
109       if (((RequestException) throwable).getErrorDetails().getHttpStatusCode() != -1) {
110         // error code has an associated http status code, mark it as HTTP_ERROR
111         return DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR;
112       }
113     }
114 
115     if (throwable.getCause() != null) {
116       // Exception has an underlying cause -- attempt mapping on that cause.
117       return mapExceptionToDownloadResultCode(throwable.getCause(), iteration + 1);
118     }
119 
120     if (throwable instanceof com.google.android.downloader.DownloadException) {
121       // If DownloadException is not wrapping anything, we can't determine the error further -- mark
122       // it as a general Downloader2 error.
123       return DownloadResultCode.ANDROID_DOWNLOADER2_ERROR;
124     }
125 
126     // We couldn't parse any common errors, return an unknown error
127     return DownloadResultCode.UNKNOWN_ERROR;
128   }
129 
130   /**
131    * Interface to handle parsing exceptions from an underlying network stack.
132    *
133    * <p>If an underlying network stack is used which can throw special exceptions or has an error
134    * code map, consider implementing this to provide better handling of exceptions.
135    */
136   public static interface NetworkStackExceptionHandler {
137     /**
138      * Map Custom Exception to {@link DownloadResultCode}.
139      *
140      * <p>Underlying network stacks may have specific exceptions that can be used to determine the
141      * best DownloadResultCode. This method should be overridden to check for such exceptions.
142      *
143      * <p>If a known {@link DownloadResultCode} is returned (i.e not UNKNOWN_ERROR), it will be
144      * used.
145      *
146      * <p>By default, an UNKNOWN_ERROR is returned.
147      */
mapFromNetworkStackException(Throwable throwable)148     default DownloadResultCode mapFromNetworkStackException(Throwable throwable) {
149       return DownloadResultCode.UNKNOWN_ERROR;
150     }
151   }
152 }
153