diff --git a/.changes/next-release/feature-AmazonS3-deae2cc.json b/.changes/next-release/feature-AmazonS3-deae2cc.json new file mode 100644 index 000000000000..b3112fb96ced --- /dev/null +++ b/.changes/next-release/feature-AmazonS3-deae2cc.json @@ -0,0 +1,6 @@ +{ + "category": "Amazon S3", + "contributor": "", + "type": "feature", + "description": "`S3Utilities#getUrl` now supports versionId. See [#2224](https://github.com/aws/aws-sdk-java-v2/issues/2224)" +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index f7aaa37a0981..e2b04d9da236 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -124,7 +124,7 @@ static S3Utilities create(SdkClientConfiguration clientConfiguration) { * * @param getUrlRequest A {@link Consumer} that will call methods on {@link GetUrlRequest.Builder} to create a request. * @return A URL for an object stored in Amazon S3. - * @throws MalformedURLException Generated Url is malformed + * @throws SdkException Generated Url is malformed */ public URL getUrl(Consumer getUrlRequest) { return getUrl(GetUrlRequest.builder().applyMutation(getUrlRequest).build()); @@ -143,7 +143,7 @@ public URL getUrl(Consumer getUrlRequest) { * * @param getUrlRequest request to construct url * @return A URL for an object stored in Amazon S3. - * @throws MalformedURLException Generated Url is malformed + * @throws SdkException Generated Url is malformed */ public URL getUrl(GetUrlRequest getUrlRequest) { Region resolvedRegion = resolveRegionForGetUrl(getUrlRequest); @@ -155,6 +155,7 @@ public URL getUrl(GetUrlRequest getUrlRequest) { GetObjectRequest getObjectRequest = GetObjectRequest.builder() .bucket(getUrlRequest.bucket()) .key(getUrlRequest.key()) + .versionId(getUrlRequest.versionId()) .build(); S3EndpointResolverContext resolverContext = S3EndpointResolverContext.builder() @@ -215,6 +216,10 @@ private SdkHttpFullRequest createMarshalledRequest(GetUrlRequest getUrlRequest, // encode key builder.encodedPath(PathMarshaller.GREEDY.marshall(builder.encodedPath(), "Key", getUrlRequest.key())); + if (getUrlRequest.versionId() != null) { + builder.appendRawQueryParameter("versionId", getUrlRequest.versionId()); + } + return builder.build(); } @@ -258,9 +263,9 @@ public Builder s3Configuration(S3Configuration s3Configuration) { } /** - * The profile file from the {@link ClientOverrideConfiguration#profileFile()}. This is private and only used when the - * utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less confusing - * to support the full {@link ClientOverrideConfiguration} object in the future. + * The profile file from the {@link ClientOverrideConfiguration#defaultProfileFile()}. This is private and only used + * when the utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less + * confusing to support the full {@link ClientOverrideConfiguration} object in the future. */ private Builder profileFile(ProfileFile profileFile) { this.profileFile = profileFile; @@ -268,9 +273,9 @@ private Builder profileFile(ProfileFile profileFile) { } /** - * The profile name from the {@link ClientOverrideConfiguration#profileName()}. This is private and only used when the - * utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less confusing - * to support the full {@link ClientOverrideConfiguration} object in the future. + * The profile name from the {@link ClientOverrideConfiguration#defaultProfileFile()}. This is private and only used + * when the utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less + * confusing to support the full {@link ClientOverrideConfiguration} object in the future. */ private Builder profileName(String profileName) { this.profileName = profileName; diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/model/GetUrlRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/model/GetUrlRequest.java index 095e97ac2962..5ca8a3a7f61b 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/model/GetUrlRequest.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/model/GetUrlRequest.java @@ -55,7 +55,16 @@ public final class GetUrlRequest implements SdkPojo, ToCopyableBuilder> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(BUCKET_FIELD, KEY_FIELD)); + private static final SdkField VERSION_ID_FIELD = SdkField + .builder(MarshallingType.STRING) + .memberName("VersionId") + .getter(getter(GetUrlRequest::versionId)) + .setter(setter(Builder::versionId)) + .traits(LocationTrait.builder().location(MarshallLocation.QUERY_PARAM).locationName("versionId") + .unmarshallLocationName("versionId").build()).build(); + + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(BUCKET_FIELD, KEY_FIELD, + VERSION_ID_FIELD)); private final String bucket; @@ -65,11 +74,14 @@ public final class GetUrlRequest implements SdkPojo, ToCopyableBuilder Optional getValueForField(String fieldName, Class clazz) { return Optional.ofNullable(clazz.cast(bucket())); case "Key": return Optional.ofNullable(clazz.cast(key())); + case "VersionId": + return Optional.ofNullable(clazz.cast(versionId())); default: return Optional.empty(); } @@ -152,6 +175,15 @@ public interface Builder extends SdkPojo, CopyableBuilder b.bucket("foo").key("bar").versionId("1")) + .toExternalForm()) + .isEqualTo("https://foo.s3.us-west-2.amazonaws.com/bar?versionId=1"); + + assertThat(utilities.getUrl(b -> b.bucket("foo").key("bar").versionId("@1")) + .toExternalForm()) + .isEqualTo("https://foo.s3.us-west-2.amazonaws.com/bar?versionId=%401"); + } + private static GetUrlRequest requestWithoutSpaces() { return GetUrlRequest.builder() .bucket("foo-bucket")