diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaService.java index 9cf13258bab88..bc2d5d54e2a19 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaService.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaService.java @@ -14,7 +14,13 @@ */ package com.microsoft.windowsazure.services.media; +import java.net.URI; + import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.media.implementation.MediaBlobContainerWriter; +import com.microsoft.windowsazure.services.media.models.LocatorInfo; +import com.microsoft.windowsazure.services.media.models.LocatorType; +import com.sun.jersey.api.client.Client; /** * @@ -64,4 +70,55 @@ public static MediaContract create(String profile) { public static MediaContract create(String profile, Configuration config) { return config.create(profile, MediaContract.class); } + + /** + * Creates an instance of the WritableBlobContainerContract API that will + * write to the blob container given by the provided locator. + * + * @param locator + * locator specifying where to upload to + * @return the implementation of WritableBlobContainerContract + */ + public static WritableBlobContainerContract createBlobWriter(LocatorInfo locator) { + if (locator.getLocatorType() != LocatorType.SAS) { + throw new IllegalArgumentException("Can only write to SAS locators"); + } + + LocatorParser p = new LocatorParser(locator); + + return new MediaBlobContainerWriter(createUploaderClient(), p.getAccountName(), p.getStorageUri(), + p.getContainer(), p.getSASToken()); + } + + /** + * Helper class to encapsulate pulling information out of the locator. + */ + private static class LocatorParser { + URI locatorPath; + + LocatorParser(LocatorInfo locator) { + locatorPath = URI.create(locator.getPath()); + } + + String getAccountName() { + return locatorPath.getHost().split("\\.")[0]; + } + + String getStorageUri() { + return locatorPath.getScheme() + "://" + locatorPath.getAuthority(); + } + + String getContainer() { + return locatorPath.getPath().substring(1); + } + + String getSASToken() { + return locatorPath.getRawQuery(); + } + } + + private static Client createUploaderClient() { + Client client = Client.create(); + return client; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/WritableBlobContainerContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/WritableBlobContainerContract.java new file mode 100644 index 0000000000000..808e5c0460ece --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/WritableBlobContainerContract.java @@ -0,0 +1,173 @@ +/** + * Copyright 2012 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.io.InputStream; + +import com.microsoft.windowsazure.services.blob.models.BlockList; +import com.microsoft.windowsazure.services.blob.models.CommitBlobBlocksOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobBlockOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobResult; +import com.microsoft.windowsazure.services.core.FilterableService; +import com.microsoft.windowsazure.services.core.ServiceException; + +/** + * Contract for uploading media files to blob storage managed + * by Media Services. + * + */ +public interface WritableBlobContainerContract extends FilterableService { + + /** + * Creates a block blob from a content stream. + * + * @param container + * A {@link String} containing the name of the container to create the blob in. + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param contentStream + * An {@link InputStream} reference to the content stream to upload to the new blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + CreateBlobResult createBlockBlob(String blob, InputStream contentStream) throws ServiceException; + + /** + * Creates a block blob from a content stream, using the specified options. + *

+ * Use the {@link CreateBlobOptions options} parameter to optionally specify the server timeout for the operation, + * the MIME content type and content encoding for the blob, the content language, the MD5 hash, a cache control + * value, and blob metadata. + * + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param contentStream + * An {@link InputStream} reference to the content to upload to the new blob. + * @param options + * A {@link CreateBlobOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + CreateBlobResult createBlockBlob(String blob, InputStream contentStream, CreateBlobOptions options) + throws ServiceException; + + /** + * Creates a new uncommited block from a content stream. + *

+ * This method creates an uncommitted block for a block blob specified by the blob and container + * parameters. The blockId parameter is a client-specified ID for the block, which must be less than or + * equal to 64 bytes in size. For a given blob, the length of the value specified for the blockId parameter + * must be the same size for each block. The contentStream parameter specifies the content to be copied to + * the block. The content for the block must be less than or equal to 4 MB in size. + *

+ * To create or update a block blob, the blocks that have been successfully written to the server with this method + * must be committed using a call to {@link WritableBlobContainerContract#commitBlobBlocks(String, BlockList)} or + * {@link WritableBlobContainerContract#commitBlobBlocks(String, BlockList, CommitBlobBlocksOptions)}. + * + * @param blob + * A {@link String} containing the name of the blob to create the block for. + * @param blockId + * A {@link String} containing a client-specified ID for the block. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the block. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + void createBlobBlock(String blob, String blockId, InputStream contentStream) throws ServiceException; + + /** + * Creates a new uncommitted block from a content stream, using the specified options. + *

+ * This method creates an uncommitted block for a block blob specified by the blob and container + * parameters. The blockId parameter is a client-specified ID for the block, which must be less than or + * equal to 64 bytes in size. For a given blob, the length of the value specified for the blockId parameter + * must be the same size for each block. The contentStream parameter specifies the content to be copied to + * the block. The content for the block must be less than or equal to 4 MB in size. Use the + * {@link CreateBlobBlockOptions options} parameter to optionally specify the server timeout for the operation, the + * lease ID if the blob has an active lease, and the MD5 hash value for the block content. + *

+ * To create or update a block blob, the blocks that have been successfully written to the server with this method + * must be committed using a call to {@link WritableBlobContainerContract#commitBlobBlocks(String, BlockList)} or + * {@link WritableBlobContainerContract#commitBlobBlocks(String, BlockList, CommitBlobBlocksOptions)}. + * + * @param blob + * A {@link String} containing the name of the blob to create the block for. + * @param blockId + * A {@link String} containing a client-specified ID for the block. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the block. + * @param options + * A {@link CreateBlobBlockOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + void createBlobBlock(String blob, String blockId, InputStream contentStream, CreateBlobBlockOptions options) + throws ServiceException; + + /** + * Commits a list of blocks to a block blob. + *

+ * This method creates or updates the block blob specified by the blob and container parameters. + * You can call this method to update a blob by uploading only those blocks that have changed, then committing the + * new and existing blocks together. You can do this with the blockList parameter by specifying whether to + * commit a block from the committed block list or from the uncommitted block list, or to commit the most recently + * uploaded version of the block, whichever list it may belong to. + *

+ * In order to be written as part of a blob, each block in the list must have been successfully written to the + * server with a call to {@link WritableBlobContainerContract#createBlobBlock(String, String, InputStream)} or + * {@link WritableBlobContainerContract#createBlobBlock(String, String, InputStream, CreateBlobBlockOptions)}. + * + * @param blob + * A {@link String} containing the name of the block blob to create or update. + * @param blockList + * A {@link BlockList} containing the list of blocks to commit to the block blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + void commitBlobBlocks(String blob, BlockList blockList) throws ServiceException; + + /** + * Commits a block list to a block blob, using the specified options. + *

+ * This method creates or updates the block blob specified by the blob and container parameters. + * You can call this method to update a blob by uploading only those blocks that have changed, then committing the + * new and existing blocks together. You can do this with the blockList parameter by specifying whether to + * commit a block from the committed block list or from the uncommitted block list, or to commit the most recently + * uploaded version of the block, whichever list it may belong to. Use the {@link CommitBlobBlocksOptions options} + * parameter to optionally specify the server timeout for the operation, the MIME content type and content encoding + * for the blob, the content language, the MD5 hash, a cache control value, blob metadata, the lease ID if the blob + * has an active lease, and any access conditions for the operation. + *

+ * In order to be written as part of a blob, each block in the list must have been successfully written to the + * server with a call to {@link WritableBlobContainerContract#createBlobBlock(String, String, InputStream)} or + * {@link WritableBlobContainerContract#createBlobBlock(String, String, InputStream, CreateBlobBlockOptions)}. + * + * @param blob + * A {@link String} containing the name of the block blob to create or update. + * @param blockList + * A {@link BlockList} containing the list of blocks to commit to the block blob. + * @param options + * A {@link CommitBlobBlocksOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ + void commitBlobBlocks(String blob, BlockList blockList, CommitBlobBlocksOptions options) throws ServiceException; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobContainerWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobContainerWriter.java new file mode 100644 index 0000000000000..d6831a3dc11cf --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobContainerWriter.java @@ -0,0 +1,118 @@ +/** + * Copyright 2012 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media.implementation; + +import java.io.InputStream; + +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.blob.implementation.BlobExceptionProcessor; +import com.microsoft.windowsazure.services.blob.models.BlockList; +import com.microsoft.windowsazure.services.blob.models.CommitBlobBlocksOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobBlockOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobOptions; +import com.microsoft.windowsazure.services.blob.models.CreateBlobResult; +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.microsoft.windowsazure.services.media.WritableBlobContainerContract; +import com.sun.jersey.api.client.Client; + +/** + * Implementation of WritableBlobContainerContract, used to upload blobs to the + * Media Services storage. + * + */ +public class MediaBlobContainerWriter implements WritableBlobContainerContract { + + private final BlobContract blobService; + private final BlobContract restProxy; + private final String containerName; + + /** + * + */ + public MediaBlobContainerWriter(Client client, String accountName, String blobServiceUri, String containerName, + String sasToken) { + this.containerName = containerName; + this.restProxy = new MediaBlobRestProxy(client, accountName, blobServiceUri, new SASTokenFilter(sasToken)); + this.blobService = new BlobExceptionProcessor(this.restProxy); + } + + private MediaBlobContainerWriter(MediaBlobContainerWriter baseWriter, ServiceFilter filter) { + this.containerName = baseWriter.containerName; + this.restProxy = baseWriter.restProxy.withFilter(filter); + this.blobService = new BlobExceptionProcessor(this.restProxy); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.core.FilterableService#withFilter(com.microsoft.windowsazure.services.core.ServiceFilter) + */ + @Override + public WritableBlobContainerContract withFilter(ServiceFilter filter) { + return new MediaBlobContainerWriter(this, filter); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#createBlockBlob(java.lang.String, java.io.InputStream) + */ + @Override + public CreateBlobResult createBlockBlob(String blob, InputStream contentStream) throws ServiceException { + return blobService.createBlockBlob(containerName, blob, contentStream); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#createBlockBlob(java.lang.String, java.io.InputStream, com.microsoft.windowsazure.services.blob.models.CreateBlobOptions) + */ + @Override + public CreateBlobResult createBlockBlob(String blob, InputStream contentStream, CreateBlobOptions options) + throws ServiceException { + return blobService.createBlockBlob(containerName, blob, contentStream, options); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#createBlobBlock(java.lang.String, java.lang.String, java.io.InputStream) + */ + @Override + public void createBlobBlock(String blob, String blockId, InputStream contentStream) throws ServiceException { + blobService.createBlobBlock(containerName, blob, blockId, contentStream); + + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#createBlobBlock(java.lang.String, java.lang.String, java.io.InputStream, com.microsoft.windowsazure.services.blob.models.CreateBlobBlockOptions) + */ + @Override + public void createBlobBlock(String blob, String blockId, InputStream contentStream, CreateBlobBlockOptions options) + throws ServiceException { + blobService.createBlobBlock(containerName, blob, blockId, contentStream, options); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#commitBlobBlocks(java.lang.String, com.microsoft.windowsazure.services.blob.models.BlockList) + */ + @Override + public void commitBlobBlocks(String blob, BlockList blockList) throws ServiceException { + blobService.commitBlobBlocks(containerName, blob, blockList); + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.media.WritableBlobContainerContract#commitBlobBlocks(java.lang.String, com.microsoft.windowsazure.services.blob.models.BlockList, com.microsoft.windowsazure.services.blob.models.CommitBlobBlocksOptions) + */ + @Override + public void commitBlobBlocks(String blob, BlockList blockList, CommitBlobBlocksOptions options) + throws ServiceException { + blobService.commitBlobBlocks(containerName, blob, blockList, options); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobRestProxy.java new file mode 100644 index 0000000000000..3f799b69cd601 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/MediaBlobRestProxy.java @@ -0,0 +1,85 @@ +/** + * Copyright 2012 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media.implementation; + +import java.util.Arrays; + +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.blob.implementation.BlobOperationRestProxy; +import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; +import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.sun.jersey.api.client.Client; + +/** + * Rest proxy for blob operations that's specialized for working + * with the blobs created by and for Media Services storage. + * + */ +class MediaBlobRestProxy extends BlobOperationRestProxy { + private final SASTokenFilter tokenFilter; + + /** + * Construct instance of MediaBlobRestProxy with given parameters. + * + * @param channel + * Jersey Client object used to communicate with blob service + * @param accountName + * Account name for blob storage + * @param url + * URL for blob storage + * @param tokenFilter + * filter used to add SAS tokens to requests. + */ + public MediaBlobRestProxy(Client channel, String accountName, String url, SASTokenFilter tokenFilter) { + super(channel, accountName, url); + + this.tokenFilter = tokenFilter; + channel.addFilter(tokenFilter); + } + + /** + * Construct instance of MediaBlobRestProxy with given parameters. + * + * @param channel + * Jersey Client object used to communicate with blob service + * @param filters + * Additional ServiceFilters to manipulate requests and responses + * @param accountName + * Account name for blob storage + * @param url + * URL for blob storage + * @param dateMapper + * date conversion helper object + */ + public MediaBlobRestProxy(Client channel, ServiceFilter[] filters, String accountName, String url, + SASTokenFilter tokenFilter, RFC1123DateConverter dateMapper) { + super(channel, filters, accountName, url, dateMapper); + + this.tokenFilter = tokenFilter; + } + + /* (non-Javadoc) + * @see com.microsoft.windowsazure.services.blob.implementation.BlobOperationRestProxy#withFilter(com.microsoft.windowsazure.services.core.ServiceFilter) + */ + @Override + public BlobContract withFilter(ServiceFilter filter) { + ServiceFilter[] currentFilters = getFilters(); + ServiceFilter[] newFilters = Arrays.copyOf(currentFilters, currentFilters.length + 1); + newFilters[currentFilters.length] = filter; + return new MediaBlobRestProxy(getChannel(), newFilters, getAccountName(), getUrl(), this.tokenFilter, + getDateMapper()); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/IntegrationTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/IntegrationTestBase.java index efc4a219ac1fa..0b88485694e21 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/IntegrationTestBase.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/IntegrationTestBase.java @@ -14,6 +14,7 @@ public void beforeEachTest() { overrideWithEnv(config, MediaConfiguration.OAUTH_URI); overrideWithEnv(config, MediaConfiguration.OAUTH_CLIENT_ID); overrideWithEnv(config, MediaConfiguration.OAUTH_CLIENT_SECRET); + overrideWithEnv(config, MediaConfiguration.OAUTH_SCOPE); } public static Configuration createConfig() { diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/UploadingIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/UploadingIntegrationTest.java new file mode 100644 index 0000000000000..11162a491ebff --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/UploadingIntegrationTest.java @@ -0,0 +1,113 @@ +/** + * Copyright 2012 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.ExponentialRetryPolicy; +import com.microsoft.windowsazure.services.core.RetryPolicyFilter; +import com.microsoft.windowsazure.services.media.models.AccessPolicyInfo; +import com.microsoft.windowsazure.services.media.models.AccessPolicyPermission; +import com.microsoft.windowsazure.services.media.models.AssetInfo; +import com.microsoft.windowsazure.services.media.models.CreateAccessPolicyOptions; +import com.microsoft.windowsazure.services.media.models.CreateAssetOptions; +import com.microsoft.windowsazure.services.media.models.CreateLocatorOptions; +import com.microsoft.windowsazure.services.media.models.LocatorInfo; +import com.microsoft.windowsazure.services.media.models.LocatorType; + +/** + * Testing uploading in various permutations. + * + */ +public class UploadingIntegrationTest extends IntegrationTestBase { + private static ArrayList locatorIds; + + @BeforeClass + public static void init() { + locatorIds = new ArrayList(); + } + + @AfterClass + public static void cleanup() throws Exception { + MediaContract service = MediaService.create(config); + deleteLocators(service); + deleteAccessPolicies(service, "upload"); + deleteAssets(service, "upload"); + } + + private static void deleteLocators(MediaContract service) throws Exception { + for (String locatorId : locatorIds) { + service.deleteLocator(locatorId); + } + } + + private static void deleteAccessPolicies(MediaContract service, String prefix) throws Exception { + List policies = service.listAccessPolicies(); + for (AccessPolicyInfo policy : policies) { + if (policy.getName().startsWith(prefix)) { + service.deleteAccessPolicy(policy.getId()); + } + } + } + + private static void deleteAssets(MediaContract service, String prefix) throws Exception { + List assets = service.listAssets(); + for (AssetInfo asset : assets) { + if (asset.getName().startsWith(prefix)) { + service.deleteAsset(asset.getId()); + } + } + } + + @Test + public void canUploadBlockBlob() throws Exception { + MediaContract service = MediaService.create(config); + + AssetInfo asset = service.createAsset(new CreateAssetOptions().setName("uploadBlockBlobAsset")); + + AccessPolicyInfo policy = service.createAccessPolicy("uploadWritePolicy", 10, + new CreateAccessPolicyOptions().addPermissions(AccessPolicyPermission.WRITE)); + + Date now = new Date(); + Date fiveMinutesAgo = new Date(); + Date tenMinutesFromNow = new Date(); + + fiveMinutesAgo.setTime(now.getTime() - (5 * 60 * 1000)); + tenMinutesFromNow.setTime(now.getTime() + (10 * 60 * 1000)); + + LocatorInfo locator = service.createLocator(policy.getId(), asset.getId(), LocatorType.SAS, + new CreateLocatorOptions().setStartTime(fiveMinutesAgo).setExpirationDateTime(tenMinutesFromNow)); + + locatorIds.add(locator.getId()); + + WritableBlobContainerContract blobWriter = MediaService.createBlobWriter(locator); + + ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(5000, 5, new int[] { 400, 404 }); + blobWriter = blobWriter.withFilter(new RetryPolicyFilter(retryPolicy)); + + InputStream blobContent = new ByteArrayInputStream(new byte[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 }); + + blobWriter.createBlockBlob("uploadBlockBlobTest", blobContent); + } +}