1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.s3.internal.handlers; 17 18 import static software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes; 19 20 import software.amazon.awssdk.annotations.SdkInternalApi; 21 import software.amazon.awssdk.core.SdkRequest; 22 import software.amazon.awssdk.core.interceptor.Context.ModifyRequest; 23 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 24 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 25 import software.amazon.awssdk.services.s3.internal.resource.S3ArnUtils; 26 import software.amazon.awssdk.services.s3.internal.resource.S3ResourceType; 27 import software.amazon.awssdk.services.s3.model.CopyObjectRequest; 28 import software.amazon.awssdk.services.s3.model.PutObjectRequest; 29 import software.amazon.awssdk.services.s3.model.UploadPartCopyRequest; 30 import software.amazon.awssdk.utils.Validate; 31 32 /** 33 * This interceptor transforms the {@code sourceBucket}, {@code sourceKey}, and {@code sourceVersionId} parameters for 34 * {@link CopyObjectRequest} and {@link UploadPartCopyRequest} into a {@code copySource} parameter. The logic needed to 35 * construct a {@code copySource} can be considered non-trivial, so this interceptor facilitates allowing users to 36 * use higher-level constructs that more closely match other APIs, like {@link PutObjectRequest}. Additionally, this 37 * interceptor is responsible for URL encoding the relevant portions of the {@code copySource} value. 38 * <p> 39 * <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html#API_CopyObject_RequestParameters">API_CopyObject_RequestParameters</a> 40 * <p> 41 * <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html#API_UploadPartCopy_RequestParameters">API_UploadPartCopy_RequestParameters</a> 42 */ 43 @SdkInternalApi 44 public final class CopySourceInterceptor implements ExecutionInterceptor { 45 46 @Override modifyRequest(ModifyRequest context, ExecutionAttributes executionAttributes)47 public SdkRequest modifyRequest(ModifyRequest context, ExecutionAttributes executionAttributes) { 48 SdkRequest request = context.request(); 49 if (request instanceof CopyObjectRequest) { 50 return modifyCopyObjectRequest((CopyObjectRequest) request); 51 } 52 if (request instanceof UploadPartCopyRequest) { 53 return modifyUploadPartCopyRequest((UploadPartCopyRequest) request); 54 } 55 return request; 56 } 57 modifyCopyObjectRequest(CopyObjectRequest request)58 private static SdkRequest modifyCopyObjectRequest(CopyObjectRequest request) { 59 if (request.copySource() != null) { 60 requireNotSet(request.sourceBucket(), "sourceBucket"); 61 requireNotSet(request.sourceKey(), "sourceKey"); 62 requireNotSet(request.sourceVersionId(), "sourceVersionId"); 63 return request; 64 } 65 String copySource = constructCopySource( 66 requireSet(request.sourceBucket(), "sourceBucket"), 67 requireSet(request.sourceKey(), "sourceKey"), 68 request.sourceVersionId() 69 ); 70 return request.toBuilder() 71 .sourceBucket(null) 72 .sourceKey(null) 73 .sourceVersionId(null) 74 .copySource(copySource) 75 .build(); 76 } 77 modifyUploadPartCopyRequest(UploadPartCopyRequest request)78 private static SdkRequest modifyUploadPartCopyRequest(UploadPartCopyRequest request) { 79 if (request.copySource() != null) { 80 requireNotSet(request.sourceBucket(), "sourceBucket"); 81 requireNotSet(request.sourceKey(), "sourceKey"); 82 requireNotSet(request.sourceVersionId(), "sourceVersionId"); 83 return request; 84 } 85 String copySource = constructCopySource( 86 requireSet(request.sourceBucket(), "sourceBucket"), 87 requireSet(request.sourceKey(), "sourceKey"), 88 request.sourceVersionId() 89 ); 90 return request.toBuilder() 91 .sourceBucket(null) 92 .sourceKey(null) 93 .sourceVersionId(null) 94 .copySource(copySource) 95 .build(); 96 } 97 constructCopySource(String sourceBucket, String sourceKey, String sourceVersionId)98 private static String constructCopySource(String sourceBucket, String sourceKey, String sourceVersionId) { 99 StringBuilder copySource = new StringBuilder(); 100 copySource.append(urlEncodeIgnoreSlashes(sourceBucket)); 101 S3ArnUtils.getArnType(sourceBucket).ifPresent(arnType -> { 102 if (arnType == S3ResourceType.ACCESS_POINT || arnType == S3ResourceType.OUTPOST) { 103 copySource.append("/object"); 104 } 105 }); 106 copySource.append("/"); 107 copySource.append(urlEncodeIgnoreSlashes(sourceKey)); 108 if (sourceVersionId != null) { 109 copySource.append("?versionId="); 110 copySource.append(urlEncodeIgnoreSlashes(sourceVersionId)); 111 } 112 return copySource.toString(); 113 } 114 requireNotSet(Object value, String paramName)115 private static void requireNotSet(Object value, String paramName) { 116 Validate.isTrue(value == null, "Parameter 'copySource' must not be used in conjunction with '%s'", 117 paramName); 118 } 119 requireSet(T value, String paramName)120 private static <T> T requireSet(T value, String paramName) { 121 Validate.isTrue(value != null, "Parameter '%s' must not be null", 122 paramName); 123 return value; 124 } 125 } 126