diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3AsyncBlobStore.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3AsyncBlobStore.java index 28121c3f5f..7f2a5b6053 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3AsyncBlobStore.java +++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3AsyncBlobStore.java @@ -232,6 +232,12 @@ public class S3AsyncBlobStore extends BaseAsyncBlobStore { */ @Override public ListenableFuture putBlob(String container, Blob blob) { + return putBlob(container, blob, PutOptions.NONE); + } + + @Override + public ListenableFuture putBlob(String container, Blob blob, PutOptions overrides) { + // TODO: Make use of options overrides PutObjectOptions options = new PutObjectOptions(); try { AccessControlList acl = bucketAcls.getUnchecked(container); @@ -256,12 +262,6 @@ public class S3AsyncBlobStore extends BaseAsyncBlobStore { return async.deleteObject(container, key); } - @Override - public ListenableFuture putBlob(String container, Blob blob, PutOptions options) { - // TODO implement options - return putBlob(container, blob); - } - @Override public ListenableFuture createContainerInLocation(Location location, String container, CreateContainerOptions options) { diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java index b8e16a00d5..6f8d01a925 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java +++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java @@ -232,15 +232,7 @@ public class S3BlobStore extends BaseBlobStore { */ @Override public String putBlob(String container, Blob blob) { - PutObjectOptions options = new PutObjectOptions(); - try { - AccessControlList acl = bucketAcls.getUnchecked(container); - if (acl != null && acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ)) - options.withAcl(CannedAccessPolicy.PUBLIC_READ); - } catch (CacheLoader.InvalidCacheLoadException e) { - // nulls not permitted from cache loader - } - return sync.putObject(container, blob2Object.apply(blob), options); + return putBlob(container, blob, PutOptions.NONE); } /** @@ -252,9 +244,17 @@ public class S3BlobStore extends BaseBlobStore { * object */ @Override - public String putBlob(String container, Blob blob, PutOptions options) { - // TODO implement options - return putBlob(container, blob); + public String putBlob(String container, Blob blob, PutOptions overrides) { + // TODO: Make use of options overrides + PutObjectOptions options = new PutObjectOptions(); + try { + AccessControlList acl = bucketAcls.getUnchecked(container); + if (acl != null && acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ)) + options.withAcl(CannedAccessPolicy.PUBLIC_READ); + } catch (CacheLoader.InvalidCacheLoadException e) { + // nulls not permitted from cache loader + } + return sync.putObject(container, blob2Object.apply(blob), options); } /** diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/BlobToObjectMetadata.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/BlobToObjectMetadata.java index 9229e62a85..7bfdfeee2e 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/BlobToObjectMetadata.java +++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/BlobToObjectMetadata.java @@ -28,9 +28,11 @@ import org.jclouds.http.HttpUtils; import org.jclouds.rest.InvocationContext; import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.s3.domain.MutableObjectMetadata; +import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl; import com.google.common.base.Function; +import org.jclouds.s3.reference.S3Headers; /** * @author Adrian Cole diff --git a/apis/s3/src/main/java/org/jclouds/s3/domain/ObjectMetadata.java b/apis/s3/src/main/java/org/jclouds/s3/domain/ObjectMetadata.java index ecefdc0800..b49cd16678 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/domain/ObjectMetadata.java +++ b/apis/s3/src/main/java/org/jclouds/s3/domain/ObjectMetadata.java @@ -38,7 +38,15 @@ import org.jclouds.io.ContentMetadata; public interface ObjectMetadata extends Comparable { public enum StorageClass { - STANDARD, REDUCED_REDUNDANCY + STANDARD, REDUCED_REDUNDANCY, UNKNOWN; + + public static StorageClass fromValue(String value) { + try { + return valueOf(value); + } catch(IllegalArgumentException e) { + return UNKNOWN; + } + } } /** diff --git a/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java b/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java index ffdb53e7fa..ddc6d7b016 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java +++ b/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java @@ -34,6 +34,7 @@ import org.jclouds.s3.domain.CannedAccessPolicy; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; +import org.jclouds.s3.domain.ObjectMetadata; /** * Contains options supported in the REST API for the PUT object operation. diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java index 20c537327c..70adcb777a 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java @@ -34,12 +34,12 @@ public class PutOptions implements Cloneable { public static final ImmutablePutOptions NONE = new ImmutablePutOptions(new PutOptions()); - private boolean multipart; + private boolean multipart = false; public PutOptions() { } - PutOptions(boolean multipart) { + public PutOptions(boolean multipart) { this.multipart = multipart; } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java index 53669f993b..fc193341b7 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java @@ -22,6 +22,7 @@ import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER; import java.util.Map; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions; import org.jclouds.javax.annotation.Nullable; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -54,9 +55,12 @@ import org.jclouds.s3.Bucket; import org.jclouds.s3.S3AsyncClient; import org.jclouds.s3.S3Client; import org.jclouds.s3.binders.BindAsHostPrefixIfConfigured; +import org.jclouds.s3.binders.BindS3ObjectMetadataToRequest; import org.jclouds.s3.domain.ObjectMetadata; +import org.jclouds.s3.domain.S3Object; import org.jclouds.s3.filters.RequestAuthorizeSignature; import org.jclouds.s3.functions.BindRegionToXmlPayload; +import org.jclouds.s3.functions.ObjectKey; import org.jclouds.s3.functions.ReturnFalseIfBucketAlreadyOwnedByYouOrIllegalState; import org.jclouds.s3.options.PutBucketOptions; import org.jclouds.s3.options.PutObjectOptions; @@ -74,6 +78,7 @@ import com.google.common.util.concurrent.ListenableFuture; @RequestFilters(RequestAuthorizeSignature.class) @BlobScope(CONTAINER) public interface AWSS3AsyncClient extends S3AsyncClient { + /** * @see S3Client#putBucketInRegion */ diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java index 5ed08d99e2..11decd9f61 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java @@ -21,30 +21,33 @@ package org.jclouds.aws.s3; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions; import org.jclouds.concurrent.Timeout; import org.jclouds.io.Payload; import org.jclouds.s3.S3Client; import org.jclouds.s3.domain.ObjectMetadata; +import org.jclouds.s3.domain.S3Object; import org.jclouds.s3.options.PutObjectOptions; /** * Provides access to amazon-specific S3 features - * + * * @author Adrian Cole * @see AWSS3AsyncClient */ @Timeout(duration = 90, timeUnit = TimeUnit.SECONDS) public interface AWSS3Client extends S3Client { + /** * This operation initiates a multipart upload and returns an upload ID. This upload ID is used * to associate all the parts in the specific multipart upload. You specify this upload ID in * each of your subsequent upload part requests (see Upload Part). You also include this upload * ID in the final request to either complete or abort the multipart upload request. - * + * *

Note

If you create an object using the multipart upload APIs, currently you cannot * copy the object between regions. - * - * + * + * * @param bucketName * namespace of the object you are to upload * @param objectMetadata @@ -61,8 +64,8 @@ public interface AWSS3Client extends S3Client { * parts will be freed. However, if any part uploads are currently in progress, those part * uploads might or might not succeed. As a result, it might be necessary to abort a given * multipart upload multiple times in order to completely free all storage consumed by all parts. - * - * + * + * * @param bucketName * namespace of the object you are deleting * @param key @@ -77,20 +80,20 @@ public interface AWSS3Client extends S3Client { * Initiate Multipart Upload) before you can upload any part. In response to your initiate * request. Amazon S3 returns an upload ID, a unique identifier, that you must include in your * upload part request. - * + * *

* Part numbers can be any number from 1 to 10,000, inclusive. A part number uniquely identifies * a part and also defines its position within the object being created. If you upload a new part * using the same part number that was used with a previous part, the previously uploaded part is * overwritten. Each part must be at least 5 MB in size, except the last part. There is no size * limit on the last part of your multipart upload. - * + * *

* To ensure that data is not corrupted when traversing the network, specify the Content-MD5 * header in the upload part request. Amazon S3 checks the part data against the provided MD5 * value. If they do not match, Amazon S3 returns an error. - * - * + * + * * @param bucketName * namespace of the object you are storing * @param key @@ -109,7 +112,7 @@ public interface AWSS3Client extends S3Client { String uploadPart(String bucketName, String key, int partNumber, String uploadId, Payload part); /** - * + * This operation completes a multipart upload by assembling previously uploaded parts. *

* You first initiate the multipart upload and then upload all parts using the Upload Parts @@ -129,7 +132,7 @@ public interface AWSS3Client extends S3Client { *

* Note that if Complete Multipart Upload fails, applications should be prepared to retry the * failed requests. - * + * * @param bucketName * namespace of the object you are deleting * @param key diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3AsyncBlobStore.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3AsyncBlobStore.java index 38848d65cd..02d72315cb 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3AsyncBlobStore.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3AsyncBlobStore.java @@ -25,9 +25,12 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import com.google.common.cache.CacheLoader; import org.jclouds.Constants; import org.jclouds.aws.s3.AWSS3AsyncClient; import org.jclouds.aws.s3.AWSS3Client; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions; import org.jclouds.aws.s3.blobstore.strategy.AsyncMultipartUploadStrategy; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.domain.Blob; @@ -37,6 +40,8 @@ import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata; import org.jclouds.blobstore.util.BlobUtils; import org.jclouds.collect.Memoized; import org.jclouds.domain.Location; +import org.jclouds.s3.S3AsyncClient; +import org.jclouds.s3.S3Client; import org.jclouds.s3.blobstore.S3AsyncBlobStore; import org.jclouds.s3.blobstore.functions.BlobToObject; import org.jclouds.s3.blobstore.functions.BucketToResourceList; @@ -49,14 +54,20 @@ import org.jclouds.s3.domain.AccessControlList; import com.google.common.base.Supplier; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.ListenableFuture; +import org.jclouds.s3.domain.CannedAccessPolicy; +import org.jclouds.s3.domain.ObjectMetadata; + +import static org.jclouds.s3.domain.ObjectMetadata.StorageClass.REDUCED_REDUNDANCY; /** - * - * @author Tibor Kiss + * + * @author Tibor Kiss, Andrei Savu */ public class AWSS3AsyncBlobStore extends S3AsyncBlobStore { private final Provider multipartUploadStrategy; + private final LoadingCache bucketAcls; + private final BlobToObject blob2Object; @Inject public AWSS3AsyncBlobStore(BlobStoreContext context, BlobUtils blobUtils, @@ -71,12 +82,39 @@ public class AWSS3AsyncBlobStore extends S3AsyncBlobStore { container2BucketListOptions, bucket2ResourceList, object2Blob, blob2ObjectGetOptions, blob2Object, object2BlobMd, fetchBlobMetadataProvider, bucketAcls); this.multipartUploadStrategy = multipartUploadStrategy; + this.bucketAcls = bucketAcls; + this.blob2Object = blob2Object; } @Override public ListenableFuture putBlob(String container, Blob blob, PutOptions options) { - // need to use a provider if the strategy object is stateful - return multipartUploadStrategy.get().execute(container, blob); + if (options.isMultipart()) { + // need to use a provider if the strategy object is stateful + return multipartUploadStrategy.get().execute(container, blob, options); + } else if (options instanceof AWSS3PutOptions && + ((AWSS3PutOptions) options).getStorageClass() == REDUCED_REDUNDANCY) { + return putBlobWithReducedRedundancy(container, blob); + + } else { + return super.putBlob(container, blob, options); + } + } + + private ListenableFuture putBlobWithReducedRedundancy(String container, Blob blob) { + AWSS3PutObjectOptions options = new AWSS3PutObjectOptions(); + try { + AccessControlList acl = bucketAcls.getUnchecked(container); + if (acl != null && acl.hasPermission(AccessControlList.GroupGranteeURI.ALL_USERS, + AccessControlList.Permission.READ)) { + options.withAcl(CannedAccessPolicy.PUBLIC_READ); + } + options.storageClass(ObjectMetadata.StorageClass.REDUCED_REDUNDANCY); + + } catch (CacheLoader.InvalidCacheLoadException e) { + // nulls not permitted from cache loader + } + return S3AsyncClient.class.cast(getContext().getProviderSpecificContext().getApi()) + .putObject(container, blob2Object.apply(blob), options); } } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobStore.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobStore.java index 8bb382d61b..f981c3c85a 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobStore.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobStore.java @@ -23,7 +23,10 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; +import com.google.common.cache.CacheLoader; import org.jclouds.aws.s3.AWSS3Client; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions; import org.jclouds.aws.s3.blobstore.strategy.MultipartUploadStrategy; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.domain.Blob; @@ -33,6 +36,7 @@ import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata; import org.jclouds.blobstore.util.BlobUtils; import org.jclouds.collect.Memoized; import org.jclouds.domain.Location; +import org.jclouds.s3.S3Client; import org.jclouds.s3.blobstore.S3BlobStore; import org.jclouds.s3.blobstore.functions.BlobToObject; import org.jclouds.s3.blobstore.functions.BucketToResourceList; @@ -44,15 +48,22 @@ import org.jclouds.s3.domain.AccessControlList; import com.google.common.base.Supplier; import com.google.common.cache.LoadingCache; +import org.jclouds.s3.domain.CannedAccessPolicy; +import org.jclouds.s3.domain.ObjectMetadata; +import org.jclouds.s3.options.PutObjectOptions; + +import static org.jclouds.s3.domain.ObjectMetadata.StorageClass.REDUCED_REDUNDANCY; /** - * Proived AWS S3 specific extensions. - * - * @author Tibor Kiss + * Provide AWS S3 specific extensions. + * + * @author Tibor Kiss, Andrei Savu */ public class AWSS3BlobStore extends S3BlobStore { private final Provider multipartUploadStrategy; + private final LoadingCache bucketAcls; + private final BlobToObject blob2Object; @Inject AWSS3BlobStore(BlobStoreContext context, BlobUtils blobUtils, Supplier defaultLocation, @@ -66,11 +77,39 @@ public class AWSS3BlobStore extends S3BlobStore { bucket2ResourceList, object2Blob, blob2ObjectGetOptions, blob2Object, object2BlobMd, fetchBlobMetadataProvider, bucketAcls); this.multipartUploadStrategy = multipartUploadStrategy; + this.bucketAcls = bucketAcls; + this.blob2Object = blob2Object; } @Override public String putBlob(String container, Blob blob, PutOptions options) { - // need to use a provider if the strategy object is stateful - return multipartUploadStrategy.get().execute(container, blob); + if (options.isMultipart()) { + // need to use a provider if the strategy object is stateful + return multipartUploadStrategy.get().execute(container, blob, options); + + } else if ((options instanceof AWSS3PutOptions) && + (((AWSS3PutOptions) options).getStorageClass() == REDUCED_REDUNDANCY)) { + return putBlobWithReducedRedundancy(container, blob); + + } else { + return super.putBlob(container, blob, options); + } + } + + private String putBlobWithReducedRedundancy(String container, Blob blob) { + AWSS3PutObjectOptions options = new AWSS3PutObjectOptions(); + try { + AccessControlList acl = bucketAcls.getUnchecked(container); + if (acl != null && acl.hasPermission(AccessControlList.GroupGranteeURI.ALL_USERS, + AccessControlList.Permission.READ)) { + options.withAcl(CannedAccessPolicy.PUBLIC_READ); + } + options.storageClass(ObjectMetadata.StorageClass.REDUCED_REDUNDANCY); + + } catch (CacheLoader.InvalidCacheLoadException e) { + // nulls not permitted from cache loader + } + return S3Client.class.cast(getContext().getProviderSpecificContext().getApi()) + .putObject(container, blob2Object.apply(blob), options); } } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java index 71c85e2eec..89db15892c 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java @@ -28,6 +28,7 @@ import org.jclouds.aws.s3.blobstore.strategy.internal.ParallelMultipartUploadStr import org.jclouds.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadStrategy; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.internal.BlobStoreContextImpl; +import org.jclouds.blobstore.options.PutOptions; import org.jclouds.s3.blobstore.S3AsyncBlobStore; import org.jclouds.s3.blobstore.S3BlobStore; import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule; diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutObjectOptions.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutObjectOptions.java new file mode 100644 index 0000000000..eadf938b5e --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutObjectOptions.java @@ -0,0 +1,71 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.jclouds.aws.s3.blobstore.options; + +import org.jclouds.s3.domain.CannedAccessPolicy; +import org.jclouds.s3.domain.ObjectMetadata; +import org.jclouds.s3.options.PutObjectOptions; +import org.jclouds.s3.reference.S3Headers; + +/** + * Contains options supported in the AWS S3 REST API for the PUT object operation + * + * @see PutObjectOptions + * @author Andrei Savu + */ +public class AWSS3PutObjectOptions extends PutObjectOptions { + + public static class Builder { + + /** + * @see AWSS3PutObjectOptions#storageClass + */ + public static AWSS3PutObjectOptions storageClass(ObjectMetadata.StorageClass storageClass) { + AWSS3PutObjectOptions options = new AWSS3PutObjectOptions(); + return options.storageClass(storageClass); + } + + /** + * @see AWSS3PutObjectOptions#withAcl + */ + public static AWSS3PutObjectOptions withAcl(CannedAccessPolicy acl) { + AWSS3PutObjectOptions options = new AWSS3PutObjectOptions(); + return options.withAcl(acl); + } + } + + private ObjectMetadata.StorageClass storageClass = ObjectMetadata.StorageClass.STANDARD; + + public AWSS3PutObjectOptions storageClass(ObjectMetadata.StorageClass storageClass) { + this.storageClass = storageClass; + if (storageClass != ObjectMetadata.StorageClass.STANDARD) { + this.replaceHeader(S3Headers.STORAGE_CLASS, this.storageClass.toString()); + } + return this; + } + + public ObjectMetadata.StorageClass getStorageClass() { + return storageClass; + } + + @Override + public AWSS3PutObjectOptions withAcl(CannedAccessPolicy acl) { + return (AWSS3PutObjectOptions) super.withAcl(acl); + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutOptions.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutOptions.java new file mode 100644 index 0000000000..e67f4a1bf6 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/options/AWSS3PutOptions.java @@ -0,0 +1,80 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.jclouds.aws.s3.blobstore.options; + +import org.jclouds.blobstore.options.PutOptions; +import org.jclouds.s3.domain.ObjectMetadata; + +/** + * Contains AWS-S3 specific options supported in the put blob operation + * + * @author Andrei Savu + */ +public class AWSS3PutOptions extends PutOptions { + + public static class Builder { + + /** + * @see AWSS3PutOptions#multipart() + */ + public static AWSS3PutOptions multipart() { + AWSS3PutOptions options = new AWSS3PutOptions(); + return (AWSS3PutOptions) options.multipart(); + } + + /** + * @see AWSS3PutOptions#storageClass + */ + public static AWSS3PutOptions storageClass(ObjectMetadata.StorageClass storageClass) { + AWSS3PutOptions options = new AWSS3PutOptions(); + return options.storageClass(storageClass); + } + } + + private ObjectMetadata.StorageClass storageClass; + + public AWSS3PutOptions() { + storageClass = ObjectMetadata.StorageClass.STANDARD; + } + + public AWSS3PutOptions(boolean multipart, ObjectMetadata.StorageClass storageClass) { + super(multipart); + this.storageClass = storageClass; + } + + public AWSS3PutOptions storageClass(ObjectMetadata.StorageClass storageClass) { + this.storageClass = storageClass; + return this; + } + + public ObjectMetadata.StorageClass getStorageClass() { + return storageClass; + } + + @Override + public AWSS3PutOptions clone() { + return new AWSS3PutOptions(isMultipart(), storageClass); + } + + @Override + public String toString() { + return "[multipart=" + isMultipart() + + " storageClass=" + storageClass + "]"; + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/AsyncMultipartUploadStrategy.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/AsyncMultipartUploadStrategy.java index ccb165e6e6..2d199a23f9 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/AsyncMultipartUploadStrategy.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/AsyncMultipartUploadStrategy.java @@ -23,6 +23,7 @@ import org.jclouds.blobstore.domain.Blob; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.ImplementedBy; +import org.jclouds.blobstore.options.PutOptions; /** * @see execute(String container, Blob blob); + ListenableFuture execute(String container, Blob blob, PutOptions options); } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/MultipartUploadStrategy.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/MultipartUploadStrategy.java index e3cf3dddea..0d60d19b78 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/MultipartUploadStrategy.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/MultipartUploadStrategy.java @@ -22,6 +22,7 @@ import org.jclouds.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadS import org.jclouds.blobstore.domain.Blob; import com.google.inject.ImplementedBy; +import org.jclouds.blobstore.options.PutOptions; /** * @see execute(final String container, final Blob blob) { + public ListenableFuture execute(final String container, final Blob blob, final PutOptions options) { return Futures.makeListenable( ioWorkerExecutor.submit(new Callable() { @Override @@ -240,7 +241,7 @@ public class ParallelMultipartUploadStrategy implements AsyncMultipartUploadStra throw rtex; } } else { - ListenableFuture futureETag = ablobstore.putBlob(container, blob); + ListenableFuture futureETag = ablobstore.putBlob(container, blob, options); return maxTime != null ? futureETag.get(maxTime,TimeUnit.SECONDS) : futureETag.get(); } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java index 51078ae1b0..1198c23466 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java @@ -31,6 +31,7 @@ import org.jclouds.aws.s3.blobstore.AWSS3BlobStore; import org.jclouds.aws.s3.blobstore.strategy.MultipartUploadStrategy; import org.jclouds.blobstore.KeyNotFoundException; import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.options.PutOptions; import org.jclouds.blobstore.reference.BlobStoreConstants; import org.jclouds.io.Payload; import org.jclouds.io.PayloadSlicer; @@ -88,7 +89,7 @@ public class SequentialMultipartUploadStrategy implements MultipartUploadStrateg } @Override - public String execute(String container, Blob blob) { + public String execute(String container, Blob blob, PutOptions options) { String key = blob.getMetadata().getName(); Payload payload = blob.getPayload(); MultipartUploadSlicingAlgorithm algorithm = new MultipartUploadSlicingAlgorithm(); @@ -125,7 +126,7 @@ public class SequentialMultipartUploadStrategy implements MultipartUploadStrateg throw rtex; } } else { - return ablobstore.putBlob(container, blob); + return ablobstore.putBlob(container, blob, options); } } } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java index e9361726c1..dd1769363c 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java @@ -21,8 +21,10 @@ package org.jclouds.aws.s3; import static com.google.common.io.ByteStreams.join; import static com.google.common.io.ByteStreams.newInputStreamSupplier; import static com.google.common.io.ByteStreams.toByteArray; +import static org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions.Builder.storageClass; import static org.jclouds.crypto.CryptoStreams.md5; import static org.jclouds.io.Payloads.newByteArrayPayload; +import static org.jclouds.s3.domain.ObjectMetadata.StorageClass; import static org.testng.Assert.assertEquals; import java.io.ByteArrayInputStream; @@ -32,6 +34,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; +import org.jclouds.aws.s3.blobstore.AWSS3BlobStore; +import org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.KeyNotFoundException; import org.jclouds.blobstore.domain.Blob; @@ -39,7 +43,9 @@ import org.jclouds.blobstore.options.PutOptions; import org.jclouds.http.BaseJettyTest; import org.jclouds.http.apachehc.config.ApacheHCHttpCommandExecutorServiceModule; import org.jclouds.io.Payload; +import org.jclouds.s3.S3Client; import org.jclouds.s3.S3ClientLiveTest; +import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.ObjectMetadataBuilder; import org.jclouds.s3.domain.S3Object; import org.testng.ITestContext; @@ -154,6 +160,31 @@ public class AWSS3ClientLiveTest extends S3ClientLiveTest { Blob blob = blobStore.blobBuilder("const.txt") .payload(new File("target/const.txt")).build(); blobStore.putBlob(containerName, blob, PutOptions.Builder.multipart()); + + } finally { + returnContainer(containerName); + } + } + + public void testPutWithReducedRedundancyStorage() throws InterruptedException { + String containerName = getContainerName(); + try { + String blobName = "test-rrs"; + BlobStore blobStore = context.getBlobStore(); + blobStore.createContainerInLocation(null, containerName); + + Blob blob = blobStore.blobBuilder(blobName).payload("something").build(); + blobStore.putBlob(containerName, blob, + storageClass(StorageClass.REDUCED_REDUNDANCY)); + + S3Client s3Client = S3Client.class.cast(context.getProviderSpecificContext().getApi()); + S3Object s3Object = s3Client.getObject(containerName, blobName); + + /** + * This fails because the storage class is not part of the server response + */ + assertEquals(s3Object.getMetadata().getStorageClass(), StorageClass.REDUCED_REDUNDANCY); + } finally { returnContainer(containerName); } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategyTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategyTest.java index a3ddb05f8b..78a4cc6710 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategyTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/strategy/internal/SequentialMultipartUploadStrategyTest.java @@ -33,6 +33,7 @@ import org.jclouds.aws.s3.blobstore.AWSS3BlobStore; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.MutableBlobMetadata; +import org.jclouds.blobstore.options.PutOptions; import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.Payload; import org.jclouds.io.PayloadSlicer; @@ -102,7 +103,7 @@ public class SequentialMultipartUploadStrategyTest { replay(ometa); SequentialMultipartUploadStrategy strategy = new SequentialMultipartUploadStrategy(ablobStore, slicer); - strategy.execute(container, blob); + strategy.execute(container, blob, PutOptions.NONE); verify(ablobStore); verify(slicer); @@ -167,7 +168,7 @@ public class SequentialMultipartUploadStrategyTest { SequentialMultipartUploadStrategy strategy = new SequentialMultipartUploadStrategy(ablobStore, slicer); try { - strategy.execute(container, blob); + strategy.execute(container, blob, PutOptions.NONE); fail("Should throw RuntimeException with TimeoutException cause!"); } catch (RuntimeException rtex) { TimeoutException timeout = Throwables2.getFirstThrowableOfType(rtex, TimeoutException.class);