View Javadoc
1   /*
2    * Copyright 2020 the original author or authors.
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 org.bremersee.data.minio;
18  
19  import io.minio.errors.BucketPolicyTooLargeException;
20  import io.minio.errors.ErrorResponseException;
21  import io.minio.errors.InsufficientDataException;
22  import io.minio.errors.InternalException;
23  import io.minio.errors.InvalidResponseException;
24  import io.minio.errors.ServerException;
25  import io.minio.errors.XmlParserException;
26  import io.minio.messages.ErrorResponse;
27  import java.io.IOException;
28  import java.util.Optional;
29  import okhttp3.Response;
30  import org.springframework.util.StringUtils;
31  
32  /**
33   * The default minio error handler.
34   *
35   * @author Christian Bremer
36   */
37  public class DefaultMinioErrorHandler extends AbstractMinioErrorHandler {
38  
39    static final String ERROR_CODE_PREFIX = "MINIO_";
40  
41    @Override
42    public MinioException map(Throwable t) {
43      if (t instanceof IllegalArgumentException) {
44        return new MinioException(
45            400,
46            ERROR_CODE_PREFIX + "BAD_REQUEST",
47            StringUtils.hasText(t.getMessage()) ? t.getMessage() : "Bad request.",
48            t);
49      }
50      if (t instanceof IOException) {
51        return new MinioException(
52            500,
53            ERROR_CODE_PREFIX + "IO_ERROR",
54            StringUtils.hasText(t.getMessage()) ? t.getMessage() : "IO operation failed.",
55            t);
56      }
57      if (t instanceof io.minio.errors.MinioException) {
58        return mapMinioException((io.minio.errors.MinioException) t);
59      }
60      return new MinioException(
61          500,
62          ERROR_CODE_PREFIX + "UNSPECIFIED",
63          StringUtils.hasText(t.getMessage()) ? t.getMessage() : "Unmapped minio error.",
64          t);
65    }
66  
67    private MinioException mapMinioException(io.minio.errors.MinioException e) {
68      int status = 500;
69      String errorCode = ERROR_CODE_PREFIX + "UNSPECIFIED";
70      String message = StringUtils.hasText(e.getMessage())
71          ? e.getMessage()
72          : "Unspecified minio error.";
73  
74      if (e instanceof BucketPolicyTooLargeException) {
75        status = 400;
76        errorCode = ERROR_CODE_PREFIX + "BUCKET_POLICY_TOO_LARGE";
77        message = e.toString();
78  
79      } else if (e instanceof ErrorResponseException) {
80        ErrorResponseException ere = (ErrorResponseException) e;
81        status = getStatus(ere);
82        errorCode = getErrorCode(ere);
83        message = getMessage(ere);
84  
85      } else if (e instanceof InsufficientDataException) {
86        status = 400;
87        errorCode = ERROR_CODE_PREFIX + "INSUFFICIENT_DATA";
88  
89      } else if (e instanceof InternalException) {
90        errorCode = ERROR_CODE_PREFIX + "INTERNAL_ERROR";
91  
92      } else if (e instanceof InvalidResponseException) {
93        errorCode = ERROR_CODE_PREFIX + "INVALID_RESPONSE";
94  
95      } else if (e instanceof ServerException) {
96        errorCode = ERROR_CODE_PREFIX + "SERVER_EXCEPTION";
97  
98      } else if (e instanceof XmlParserException) {
99        errorCode = ERROR_CODE_PREFIX + "XML_PARSER_ERROR";
100     }
101     return new MinioException(status, errorCode, message, e);
102   }
103 
104   private int getStatus(ErrorResponseException e) {
105     return Optional.of(e)
106         .map(ErrorResponseException::errorResponse)
107         .map(ErrorResponse::code)
108         .map(code -> getStatus(code, e.response()))
109         .orElse(500);
110   }
111 
112   private int getStatus(String errorCode, Response response) {
113     // see https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
114     switch (errorCode) {
115       case "AmbiguousGrantByEmailAddress":
116       case "AuthorizationHeaderMalformed":
117       case "BadDigest":
118       case "CredentialsNotSupported":
119       case "EntityTooSmall":
120       case "EntityTooLarge":
121       case "ExpiredToken":
122       case "IllegalLocationConstraintException":
123       case "IllegalVersioningConfigurationException":
124       case "IncompleteBody":
125       case "IncorrectNumberOfFilesInPostRequest":
126       case "InlineDataTooLarge":
127       case "InvalidAccessPoint":
128       case "InvalidArgument":
129       case "InvalidBucketName":
130       case "InvalidDigest":
131       case "InvalidEncryptionAlgorithmError":
132       case "InvalidLocationConstraint":
133       case "InvalidPart":
134       case "InvalidPartOrder":
135       case "InvalidPolicyDocument":
136       case "InvalidRequest":
137       case "InvalidSOAPRequest":
138       case "InvalidStorageClass":
139       case "InvalidTargetBucketForLogging":
140       case "InvalidToken":
141       case "InvalidURI":
142       case "KeyTooLongError":
143       case "MalformedACLError":
144       case "MalformedPOSTRequest":
145       case "MalformedXML":
146       case "MaxMessageLengthExceeded":
147       case "MaxPostPreDataLengthExceededError":
148       case "MetadataTooLarge":
149       case "MissingRequestBodyError":
150       case "MissingSecurityElement":
151       case "MissingSecurityHeader":
152       case "NoLoggingStatusForKey":
153       case "RequestIsNotMultiPartContent":
154       case "RequestTimeout":
155       case "RequestTorrentOfBucketError":
156       case "ServerSideEncryptionConfigurationNotFoundError":
157       case "TokenRefreshRequired":
158       case "TooManyAccessPoints":
159       case "TooManyBuckets":
160       case "UnexpectedContent":
161       case "UnresolvableGrantByEmailAddress":
162       case "UserKeyMustBeSpecified":
163       case "InvalidTag":
164       case "MalformedPolicy":
165         return 400;
166       case "UnauthorizedAccess":
167         return 401;
168       case "AccessDenied":
169       case "AccountProblem":
170       case "AllAccessDisabled":
171       case "CrossLocationLoggingProhibited":
172       case "InvalidAccessKeyId":
173       case "InvalidObjectState":
174       case "InvalidPayer":
175       case "InvalidSecurity":
176       case "NotSignedUp":
177       case "RequestTimeTooSkewed":
178       case "SignatureDoesNotMatch":
179         return 403;
180       case "ResourceNotFound": // not in list
181       case "NoSuchAccessPoint":
182       case "NoSuchBucket":
183       case "NoSuchKey":
184       case "NoSuchObject": // not in list
185       case "NoSuchUpload":
186       case "NoSuchVersion":
187       case "NoSuchLifecycleConfiguration":
188       case "NoSuchBucketPolicy":
189       case "NoSuchObjectLockConfiguration": // not in list
190       case "NoSuchOutpost":
191       case "NoSuchTagSet":
192       case "UnsupportedOperation":
193         return 404;
194       case "MethodNotAllowed":
195         return 405;
196       case "BucketAlreadyExists":
197       case "BucketAlreadyOwnedByYou":
198       case "BucketNotEmpty":
199       case "InvalidBucketState":
200       case "OperationAborted":
201       case "InvalidOutpostState":
202         return 409;
203       case "MissingContentLength":
204         return 411;
205       case "PreconditionFailed":
206         return 412;
207       case "InvalidRange":
208         return 416;
209       case "InternalError":
210         return 500;
211       case "NotImplemented":
212         return 501;
213       case "ServiceUnavailable":
214       case "SlowDown":
215         return 503;
216       default:
217         return Optional.ofNullable(response)
218             .map(Response::code)
219             .filter(code -> code >= 400)
220             .orElse(400);
221     }
222   }
223 
224   private String getErrorCode(ErrorResponseException e) {
225     return Optional.of(e)
226         .map(ErrorResponseException::errorResponse)
227         .map(ErrorResponse::code)
228         .orElse(ERROR_CODE_PREFIX + "UNSPECIFIED_ERROR_RESPONSE");
229   }
230 
231   private String getMessage(ErrorResponseException e) {
232     return Optional.of(e)
233         .map(ErrorResponseException::errorResponse)
234         .map(ErrorResponse::message)
235         .orElseGet(() -> StringUtils.hasText(e.getMessage())
236             ? e.getMessage()
237             : "Unspecified error response.");
238   }
239 
240 }