diff --git a/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/after/src/main/java/foo/bar/S3Transforms.java b/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/after/src/main/java/foo/bar/S3Transforms.java index 790ae0442af0..477446b0d64d 100644 --- a/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/after/src/main/java/foo/bar/S3Transforms.java +++ b/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/after/src/main/java/foo/bar/S3Transforms.java @@ -16,7 +16,6 @@ package foo.bar; import com.amazonaws.HttpMethod; -import com.amazonaws.services.s3.model.MultiFactorAuthentication; import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams; import com.amazonaws.services.s3.model.SSECustomerKey; import java.io.ByteArrayInputStream; @@ -26,22 +25,38 @@ import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.AccessControlPolicy; +import software.amazon.awssdk.services.s3.model.BucketCannedACL; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GeneratePresignedUrlRequest; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; +import software.amazon.awssdk.services.s3.model.ObjectCannedACL; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.model.UploadRequest; public class S3Transforms { + S3Client s3; + String bucket; + String key; + void s3Pojos() { + MultiFactorAuthentication mfa = /*AWS SDK for Java v2 migration: v2 does not have a MultiFactorAuthentication POJO. Please manually set the String value on the request POJO.*/new MultiFactorAuthentication("serialNum", "token"); + DeleteObjectRequest deleteVersionRequest = - DeleteObjectRequest.builder().bucket("bucket").key("key").versionId("id").mfa(new MultiFactorAuthentication("serialNum", "token")) + DeleteObjectRequest.builder().bucket("bucket").key("key").versionId("id").mfa(mfa) .build(); } - void upload_streamWithLiteralLength(S3TransferManager tm, String bucket, String key) { + void setBucketAcl() { + /*AWS SDK for Java v2 migration: Transform for AccessControlList and CannedAccessControlList not supported. In v2, CannedAccessControlList is replaced by BucketCannedACL for buckets and ObjectCannedACL for objects.*/s3.setBucketAcl(bucket, ObjectCannedACL.AUTHENTICATED_READ); + } + + void setObjectAcl() { + /*AWS SDK for Java v2 migration: Transform for AccessControlList and CannedAccessControlList not supported. In v2, CannedAccessControlList is replaced by BucketCannedACL for buckets and ObjectCannedACL for objects.*/s3.setObjectAcl(bucket, key, ObjectCannedACL.PUBLIC_READ); + } + + void upload_streamWithLiteralLength(S3TransferManager tm) { HeadObjectResponse metadata = HeadObjectResponse.builder() .build(); InputStream inputStream = new ByteArrayInputStream(("HelloWorld").getBytes()); @@ -50,7 +65,7 @@ void upload_streamWithLiteralLength(S3TransferManager tm, String bucket, String /*AWS SDK for Java v2 migration: When using InputStream to upload with TransferManager, you must specify Content-Length and ExecutorService.*/tm.upload(UploadRequest.builder().putObjectRequest(requestWithStreamAndLiteralLength).requestBody(AsyncRequestBody.fromInputStream(inputStream, 333, newExecutorServiceVariableToDefine)).build()); } - void upload_streamWithAssignedLength(S3TransferManager tm, String bucket, String key) { + void upload_streamWithAssignedLength(S3TransferManager tm) { HeadObjectResponse metadata = HeadObjectResponse.builder() .build(); long contentLen = 777; @@ -60,7 +75,7 @@ void upload_streamWithAssignedLength(S3TransferManager tm, String bucket, String /*AWS SDK for Java v2 migration: When using InputStream to upload with TransferManager, you must specify Content-Length and ExecutorService.*/tm.upload(UploadRequest.builder().putObjectRequest(requestWithStreamAndAssignedLength).requestBody(AsyncRequestBody.fromInputStream(inputStream, contentLen, newExecutorServiceVariableToDefine)).build()); } - void upload_streamWithoutLength(S3TransferManager tm, String bucket, String key) { + void upload_streamWithoutLength(S3TransferManager tm) { InputStream inputStream = new ByteArrayInputStream(("HelloWorld").getBytes()); PutObjectRequest requestWithStreamAndNoLength = PutObjectRequest.builder().bucket(bucket).key(key).websiteRedirectLocation("location") .build(); @@ -99,7 +114,7 @@ void objectmetadata_unsupportedSetters(Date dateVal) { /*AWS SDK for Java v2 migration: Transform for ObjectMetadata setter - addUserMetadata - is not supported, please manually migrate the code by setting it on the v2 request/response object.*/metadata.addUserMetadata("a", "b"); } - private void generatePresignedUrl(S3Client s3, String bucket, String key, Date expiration) { + private void generatePresignedUrl(Date expiration) { URL urlHead = /*AWS SDK for Java v2 migration: S3 generatePresignedUrl() with HEAD HTTP method is not supported in v2. Only GET, PUT, and DELETE are supported - https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/presigner/S3Presigner.html*/s3.generatePresignedUrl(bucket, key, expiration, HttpMethod.HEAD); URL urlPatch = /*AWS SDK for Java v2 migration: S3 generatePresignedUrl() with PATCH HTTP method is not supported in v2. Only GET, PUT, and DELETE are supported - https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/presigner/S3Presigner.html*/s3.generatePresignedUrl(bucket, key, expiration, HttpMethod.PATCH); diff --git a/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/before/src/main/java/foo/bar/S3Transforms.java b/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/before/src/main/java/foo/bar/S3Transforms.java index 82d9be5763aa..7a75bdb061d4 100644 --- a/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/before/src/main/java/foo/bar/S3Transforms.java +++ b/test/v2-migration-tests/src/test/resources/software/amazon/awssdk/v2migrationtests/maven-nocompile/before/src/main/java/foo/bar/S3Transforms.java @@ -18,6 +18,7 @@ import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AccessControlList; +import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.DeleteVersionRequest; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.MultiFactorAuthentication; @@ -33,12 +34,26 @@ public class S3Transforms { + AmazonS3 s3; + String bucket; + String key; + void s3Pojos() { + MultiFactorAuthentication mfa = new MultiFactorAuthentication("serialNum", "token"); + DeleteVersionRequest deleteVersionRequest = - new DeleteVersionRequest("bucket", "key", "id", new MultiFactorAuthentication("serialNum", "token")); + new DeleteVersionRequest("bucket", "key", "id", mfa); + } + + void setBucketAcl() { + s3.setBucketAcl(bucket, CannedAccessControlList.AuthenticatedRead); + } + + void setObjectAcl() { + s3.setObjectAcl(bucket, key, CannedAccessControlList.PublicRead); } - void upload_streamWithLiteralLength(TransferManager tm, String bucket, String key) { + void upload_streamWithLiteralLength(TransferManager tm) { ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(333); InputStream inputStream = new ByteArrayInputStream(("HelloWorld").getBytes()); @@ -47,7 +62,7 @@ void upload_streamWithLiteralLength(TransferManager tm, String bucket, String ke tm.upload(requestWithStreamAndLiteralLength); } - void upload_streamWithAssignedLength(TransferManager tm, String bucket, String key) { + void upload_streamWithAssignedLength(TransferManager tm) { ObjectMetadata metadata = new ObjectMetadata(); long contentLen = 777; metadata.setContentLength(contentLen); @@ -57,7 +72,7 @@ void upload_streamWithAssignedLength(TransferManager tm, String bucket, String k tm.upload(requestWithStreamAndAssignedLength); } - void upload_streamWithoutLength(TransferManager tm, String bucket, String key) { + void upload_streamWithoutLength(TransferManager tm) { InputStream inputStream = new ByteArrayInputStream(("HelloWorld").getBytes()); PutObjectRequest requestWithStreamAndNoLength = new PutObjectRequest(bucket, key, "location"); requestWithStreamAndNoLength.setInputStream(inputStream); @@ -90,7 +105,7 @@ void objectmetadata_unsupportedSetters(Date dateVal) { metadata.addUserMetadata("a", "b"); } - private void generatePresignedUrl(AmazonS3 s3, String bucket, String key, Date expiration) { + private void generatePresignedUrl(Date expiration) { URL urlHead = s3.generatePresignedUrl(bucket, key, expiration, HttpMethod.HEAD); URL urlPatch = s3.generatePresignedUrl(bucket, key, expiration, HttpMethod.PATCH); diff --git a/v2-migration/src/main/java/software/amazon/awssdk/v2migration/S3AddImportsAndComments.java b/v2-migration/src/main/java/software/amazon/awssdk/v2migration/S3AddImportsAndComments.java new file mode 100644 index 000000000000..1ead26a398dc --- /dev/null +++ b/v2-migration/src/main/java/software/amazon/awssdk/v2migration/S3AddImportsAndComments.java @@ -0,0 +1,205 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.v2migration; + +import static software.amazon.awssdk.v2migration.internal.utils.S3TransformUtils.V1_S3_MODEL_PKG; +import static software.amazon.awssdk.v2migration.internal.utils.S3TransformUtils.V2_S3_MODEL_PKG; +import static software.amazon.awssdk.v2migration.internal.utils.S3TransformUtils.createComments; +import static software.amazon.awssdk.v2migration.internal.utils.S3TransformUtils.v1S3MethodMatcher; + +import java.util.List; +import java.util.regex.Pattern; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.RemoveImport; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import software.amazon.awssdk.annotations.SdkInternalApi; + +@SdkInternalApi +public class S3AddImportsAndComments extends Recipe { + + private static final MethodMatcher CREATE_BUCKET = v1S3MethodMatcher("createBucket(String, " + + V1_S3_MODEL_PKG + "Region"); + private static final MethodMatcher LIST_NEXT_BATCH_OBJECTS = v1S3MethodMatcher("listNextBatchOfObjects(..)"); + private static final MethodMatcher LIST_NEXT_BATCH_VERSIONS = v1S3MethodMatcher("listNextBatchOfVersions(..)"); + private static final MethodMatcher GET_METADATA = v1S3MethodMatcher("getCachedResponseMetadata(..)"); + private static final MethodMatcher SET_BUCKET_ACL = v1S3MethodMatcher("setBucketAcl(..)"); + private static final MethodMatcher SET_ENDPOINT = v1S3MethodMatcher("setEndpoint(..)"); + private static final MethodMatcher SET_OBJECT_ACL = v1S3MethodMatcher("setObjectAcl(..)"); + private static final MethodMatcher SET_REGION = v1S3MethodMatcher("setRegion(..)"); + private static final MethodMatcher SET_S3CLIENT_OPTIONS = v1S3MethodMatcher("setS3ClientOptions(..)"); + private static final MethodMatcher SELECT_OBJECT_CONTENT = v1S3MethodMatcher("selectObjectContent(..)"); + + private static final Pattern CANNED_ACL = Pattern.compile(V1_S3_MODEL_PKG + "CannedAccessControlList"); + private static final Pattern GET_OBJECT_REQUEST = Pattern.compile(V1_S3_MODEL_PKG + "GetObjectRequest"); + private static final Pattern INITIATE_MPU = Pattern.compile(V1_S3_MODEL_PKG + "InitiateMultipartUpload"); + private static final Pattern MULTI_FACTOR_AUTH = Pattern.compile(V1_S3_MODEL_PKG + "MultiFactorAuthentication"); + + @Override + public String getDisplayName() { + return "Add imports and comments to unsupported S3 transforms."; + } + + @Override + public String getDescription() { + return "Add imports and comments to unsupported S3 transforms."; + } + + @Override + public TreeVisitor getVisitor() { + return new Visitor(); + } + + private static class Visitor extends JavaIsoVisitor { + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + boolean isSetObjectAcl = SET_OBJECT_ACL.matches(method); + boolean isSetBucketAcl = SET_BUCKET_ACL.matches(method); + + if (isSetObjectAcl || isSetBucketAcl) { + removeV1S3ModelImport("CannedAccessControlList"); + maybeAddV2CannedAclImport(method.getArguments(), isSetObjectAcl, isSetBucketAcl); + + // TODO: add the developer guide link in the comments once the doc is published. + String comment = "Transform for AccessControlList and CannedAccessControlList not supported. " + + "In v2, CannedAccessControlList is replaced by BucketCannedACL for buckets and " + + "ObjectCannedACL for objects."; + return method.withComments(createComments(comment)); + } + if (LIST_NEXT_BATCH_OBJECTS.matches(method)) { + // TODO: add the developer guide link in the comments once the doc is published. + String comment = "Transform for listNextBatchOfObjects method not supported. " + + "listNextBatchOfObjects() only exists in SDK v1, for SDK v2 use either " + + "listObjectsV2Paginator().stream() for automatic pagination" + + " or manually handle pagination with listObjectsV2() and nextToken in the response for more " + + "control"; + return method.withComments(createComments(comment)); + } + if (LIST_NEXT_BATCH_VERSIONS.matches(method)) { + // TODO: add the developer guide link in the comments once the doc is published. + String comment = "Transform for listNextBatchOfVersions method not supported." + + "listNextBatchOfVersions() only exists in SDK v1, for SDK v2 use either " + + "listObjectVersionsPaginator().stream for automatic pagination" + + " or manually handle pagination with listObjectVersions() and VersionIdMarker/KeyMarker. "; + return method.withComments(createComments(comment)); + } + if (SET_REGION.matches(method)) { + String comment = "Transform for setRegion method not supported. Please manually " + + "migrate your code by configuring the region in the s3 client builder"; + return method.withComments(createComments(comment)); + } + if (SET_S3CLIENT_OPTIONS.matches(method)) { + String comment = "Transform for setS3ClientOptions method not supported. Please manually " + + "migrate setS3ClientOptions by configuring the equivalent settings in " + + "S3Configuration.builder() when building your S3Client."; + return method.withComments(createComments(comment)); + } + if (SELECT_OBJECT_CONTENT.matches(method)) { + String comment = "Note: selectObjectContent is only supported in AWS SDK v2 with S3AsyncClient. " + + "Please manually migrate to event-based response handling using " + + "SelectObjectContentEventStream"; + return method.withComments(createComments(comment)); + } + + if (CREATE_BUCKET.matches(method)) { + String comment = "Transform for createBucket(String bucketName, Region region) method not supported. Please " + + "manually migrate your code by using the following pattern: " + + "createBucket(builder -> builder.bucket(bucketName)" + + ".createBucketConfiguration(cfg -> cfg.locationConstraint(region)))"; + return method.withComments(createComments(comment)); + } + + if (SET_ENDPOINT.matches(method)) { + String comment = "Transform for setEndpoint method not supported. setEndpoint() method is removed in SDK v2. " + + "Please manually migrate your code by using endpointOverride(URI.create(endpoint)) in " + + "S3ClientBuilder"; + return method.withComments(createComments(comment)); + } + + if (GET_METADATA.matches(method)) { + String comment = "Transform for getCachedResponseMetadata method not " + + "supported. getCachedResponseMetadata() is removed in SDK v2. Please manually migrate your " + + "code by accessing metadata directly from specific response objects instead of cached " + + "metadata"; + return method.withComments(createComments(comment)); + } + + return method; + } + + @Override + public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { + JavaType type = newClass.getType(); + if (!(type instanceof JavaType.FullyQualified)) { + return newClass; + } + + if (type.isAssignableFrom(MULTI_FACTOR_AUTH)) { + removeV1S3ModelImport("MultiFactorAuthentication"); + String comment = "v2 does not have a MultiFactorAuthentication POJO. Please manually set the String value on " + + "the request POJO."; + return newClass.withComments(createComments(comment)); + } + + if (type.isAssignableFrom(GET_OBJECT_REQUEST) && newClass.getArguments().size() == 1) { + removeV1S3ModelImport("S3ObjectId"); + String comment = "v2 does not have S3ObjectId class. Please manually migrate the code by setting the configs " + + "directly into the request builder pattern."; + return newClass.withComments(createComments(comment)); + } + + if (type.isAssignableFrom(INITIATE_MPU) && newClass.getArguments().size() == 3) { + String comment = "Transform for ObjectMetadata in initiateMultipartUpload() method is not supported. Please " + + "manually migrate your code by replacing ObjectMetadata with individual setter methods " + + "or metadata map in the request builder."; + return newClass.withComments(createComments(comment)); + } + + return newClass; + } + + private void maybeAddV2CannedAclImport(List args, boolean isSetObjectAcl, boolean isSetBucketAcl) { + for (Expression expr : args) { + JavaType type = expr.getType(); + if (type == null || !type.isAssignableFrom(CANNED_ACL)) { + continue; + } + removeV1S3ModelImport("CannedAccessControlList"); + if (isSetBucketAcl) { + addV2S3ModelImport("BucketCannedACL"); + } + if (isSetObjectAcl) { + addV2S3ModelImport("ObjectCannedACL"); + } + } + } + + private void removeV1S3ModelImport(String className) { + doAfterVisit(new RemoveImport<>(V1_S3_MODEL_PKG + className, true)); + } + + private void addV2S3ModelImport(String className) { + doAfterVisit(new AddImport<>(V2_S3_MODEL_PKG + className, null, false)); + } + } +} diff --git a/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2-with-tm.yml b/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2-with-tm.yml index b1b29f80ad3f..9b85b89558ca 100644 --- a/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2-with-tm.yml +++ b/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2-with-tm.yml @@ -23,6 +23,7 @@ tags: recipeList: - software.amazon.awssdk.v2migration.AddTransferManagerDependency - software.amazon.awssdk.v2migration.UpgradeSdkDependencies + - software.amazon.awssdk.v2migration.S3AddImportsAndComments - software.amazon.awssdk.v2migration.S3StreamingResponseToV2 - software.amazon.awssdk.v2migration.S3StreamingRequestToV2 - software.amazon.awssdk.v2migration.S3NonStreamingRequestToV2 diff --git a/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2.yml b/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2.yml index 3b1aec3c6677..2652041fc569 100644 --- a/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2.yml +++ b/v2-migration/src/main/resources/META-INF/rewrite/aws-sdk-java-v1-to-v2.yml @@ -22,6 +22,7 @@ tags: - sdk recipeList: - software.amazon.awssdk.v2migration.UpgradeSdkDependencies + - software.amazon.awssdk.v2migration.S3AddImportsAndComments - software.amazon.awssdk.v2migration.S3StreamingResponseToV2 - software.amazon.awssdk.v2migration.S3StreamingRequestToV2 - software.amazon.awssdk.v2migration.S3NonStreamingRequestToV2