Skip to content

Commit d05c466

Browse files
Add error details provider for Amazon S3 plugin
1 parent bbd7daf commit d05c466

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

pom.xml

+26-2
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,17 @@
278278
<artifactId>httpclient</artifactId>
279279
<version>${httpclient.version}</version>
280280
</dependency>
281+
<dependency>
282+
<groupId>org.apache.hadoop</groupId>
283+
<artifactId>hadoop-aws</artifactId>
284+
<version>${hadoop.version}</version>
285+
<exclusions>
286+
<exclusion>
287+
<groupId>com.amazonaws</groupId>
288+
<artifactId>aws-java-sdk-bundle</artifactId>
289+
</exclusion>
290+
</exclusions>
291+
</dependency>
281292
<dependency>
282293
<groupId>com.amazonaws</groupId>
283294
<artifactId>aws-java-sdk-s3</artifactId>
@@ -288,6 +299,11 @@
288299
<artifactId>aws-java-sdk-sts</artifactId>
289300
<version>${aws.sdk.version}</version>
290301
</dependency>
302+
<dependency>
303+
<groupId>org.apache.commons</groupId>
304+
<artifactId>commons-lang3</artifactId>
305+
<version>3.12.0</version>
306+
</dependency>
291307
</dependencies>
292308

293309
<build>
@@ -310,7 +326,11 @@
310326
<extensions>true</extensions>
311327
<configuration>
312328
<instructions>
313-
<_exportcontents>io.cdap.plugin.aws.s3.*</_exportcontents>
329+
<_exportcontents>
330+
io.cdap.plugin.aws.s3.*;
331+
org.apache.hadoop.fs.s3a.*;
332+
org.apache.hadoop.fs.s3native.*;
333+
</_exportcontents>
314334
<Embed-Dependency>*;inline=false;scope=compile</Embed-Dependency>
315335
<Embed-Transitive>true</Embed-Transitive>
316336
<Embed-Directory>lib</Embed-Directory>
@@ -492,7 +512,11 @@
492512
<extensions>true</extensions>
493513
<configuration>
494514
<instructions>
495-
<_exportcontents>io.cdap.plugin.aws.s3.*</_exportcontents>
515+
<_exportcontents>
516+
io.cdap.plugin.aws.s3.*;
517+
org.apache.hadoop.fs.s3a.*;
518+
org.apache.hadoop.fs.s3native.*;
519+
</_exportcontents>
496520
<Embed-Dependency>*;inline=false;scope=compile</Embed-Dependency>
497521
<Embed-Transitive>true</Embed-Transitive>
498522
<Embed-Directory>lib</Embed-Directory>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.aws.s3.common;
18+
19+
import com.amazonaws.AmazonServiceException;
20+
import com.amazonaws.services.s3.model.AmazonS3Exception;
21+
import com.google.common.base.Throwables;
22+
import io.cdap.cdap.api.exception.ErrorCategory;
23+
import io.cdap.cdap.api.exception.ErrorCodeType;
24+
import io.cdap.cdap.api.exception.ErrorType;
25+
import io.cdap.cdap.api.exception.ErrorUtils;
26+
import io.cdap.cdap.api.exception.ErrorUtils.ActionErrorPair;
27+
import io.cdap.cdap.api.exception.ProgramFailureException;
28+
import io.cdap.cdap.etl.api.exception.ErrorContext;
29+
import io.cdap.cdap.etl.api.exception.ErrorDetailsProvider;
30+
import java.util.List;
31+
32+
/**
33+
* Error details provided for the Amazon S3
34+
**/
35+
public final class AmazonErrorDetailsProvider implements ErrorDetailsProvider {
36+
37+
static final String S3_EXTERNAL_DOC =
38+
"https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html";
39+
40+
@Override
41+
public ProgramFailureException getExceptionDetails(Exception e, ErrorContext errorContext) {
42+
List<Throwable> causalChain = Throwables.getCausalChain(e);
43+
for (Throwable t : causalChain) {
44+
if (t instanceof ProgramFailureException) {
45+
// if causal chain already has program failure exception, return null to avoid double wrap.
46+
return null;
47+
}
48+
if (t instanceof AmazonS3Exception) {
49+
AmazonS3Exception amazonServiceException = (AmazonS3Exception) t;
50+
int statusCode = amazonServiceException.getStatusCode();
51+
ActionErrorPair pair = ErrorUtils.getActionErrorByStatusCode(statusCode);
52+
String errorReason = String.format("%s %s. %s. For more details, see %s", statusCode,
53+
amazonServiceException.getErrorMessage(), pair.getCorrectiveAction(), S3_EXTERNAL_DOC);
54+
return ErrorUtils.getProgramFailureException(new ErrorCategory(
55+
ErrorCategory.ErrorCategoryEnum.PLUGIN, amazonServiceException.getErrorCode()),
56+
errorReason, amazonServiceException.getMessage(), ErrorType.USER, true,
57+
ErrorCodeType.HTTP, String.valueOf(statusCode), S3_EXTERNAL_DOC, t);
58+
}
59+
}
60+
return null;
61+
}
62+
}

src/main/java/io/cdap/plugin/aws/s3/sink/S3BatchSink.java

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import io.cdap.cdap.etl.api.batch.BatchSink;
3232
import io.cdap.cdap.etl.api.batch.BatchSinkContext;
3333
import io.cdap.cdap.etl.api.connector.Connector;
34+
import io.cdap.plugin.aws.s3.common.AmazonErrorDetailsProvider;
3435
import io.cdap.plugin.aws.s3.common.S3ConnectorConfig;
3536
import io.cdap.plugin.aws.s3.common.S3Constants;
3637
import io.cdap.plugin.aws.s3.common.S3Path;
@@ -87,6 +88,11 @@ protected LineageRecorder getLineageRecorder(BatchSinkContext context) {
8788
return new LineageRecorder(context, asset);
8889
}
8990

91+
@Override
92+
protected String getErrorDetailsProviderClassName() {
93+
return AmazonErrorDetailsProvider.class.getName();
94+
}
95+
9096
@Override
9197
protected Map<String, String> getFileSystemProperties(BatchSinkContext context) {
9298
// when context is null, it is configure time, by that time, it will always use s3n

src/main/java/io/cdap/plugin/aws/s3/source/S3BatchSource.java

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.cdap.cdap.etl.api.batch.BatchSource;
3131
import io.cdap.cdap.etl.api.batch.BatchSourceContext;
3232
import io.cdap.cdap.etl.api.connector.Connector;
33+
import io.cdap.plugin.aws.s3.common.AmazonErrorDetailsProvider;
3334
import io.cdap.plugin.aws.s3.common.S3ConnectorConfig;
3435
import io.cdap.plugin.aws.s3.common.S3Constants;
3536
import io.cdap.plugin.aws.s3.common.S3EmptyInputFormat;
@@ -89,6 +90,11 @@ public void prepareRun(BatchSourceContext context) throws Exception {
8990
super.prepareRun(context);
9091
}
9192

93+
@Override
94+
protected String getErrorDetailsProviderClassName() {
95+
return AmazonErrorDetailsProvider.class.getName();
96+
}
97+
9298
@Override
9399
protected LineageRecorder getLineageRecorder(BatchSourceContext context) {
94100
return new LineageRecorder(context, asset);

0 commit comments

Comments
 (0)