diff --git a/.gitignore b/.gitignore index 9efb40491fcf0..caca4b1a287be 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ tmp/**/* *~.nib local.properties .settings/** +*/.settings/** .loadpath # External tool builders diff --git a/BreakingChanges.txt b/BreakingChanges.txt index b04d132a364c0..e6c26191492f8 100644 --- a/BreakingChanges.txt +++ b/BreakingChanges.txt @@ -1,3 +1,32 @@ +Changes in 3.0.0 + +BLOB + * Removed getSubDirectoryReference(). Use getDirectoryReference() instead. + * Changed maxResults argument of CloudBlobDirectory.ListBlobsSegmented() to type Integer instead of int. + * Deprecated startCopyFromBlob() on CloudBlob. Use startCopy() instead. + * Deprecated blob and container constructors which take service clients in favor of constructors which take credentials. + * Invalid lease duration and break period values will cause a client-side StorageException rather than failing on the service. + +QUEUE + * Deprecated queue constructors which take service clients in favor of constructors which take credentials. + +TABLE + * Removed getEntityClass() in TableQuery. Please use getClazzType() instead. + * Deprecated the setters for timestamp as this property is only modifiable by the service. + * Deprecated table constructors which take service clients in favor of constructors which take credentials. + +FILE + * Removed getSubDirectoryReference(). Use getDirectoryReference() instead. + * Changed exception thrown by requesting a file's name from a URI with no file name from StorageException to IllegalArgumentException. + * Deprecated file, directory and share constructors which take service clients in favor of constructors which take credentials. + +OTHER + * Removed deprecated AuthenticationScheme and its getter and setter. In the future only SharedKey will be used. + * Removed deprecated getter/setters for all request option properties on the service clients. Please use the default request options getter/setters instead. + * Deprecated the Credentials and StorageKey classes. Please use the appropriate methods on StorageCredentialsAccountAndKey instead. + * Changed library behavior to retry all exceptions thrown when parsing a response object. + * Changed behavior to stop removing query parameters passed in with the resource URI if that URI contains a SAS token. Some query parameters such as comp, restype, snapshot and api-version will still be removed. + Changes in 2.0.0 BLOB diff --git a/ChangeLog.txt b/ChangeLog.txt index 08be066a47eb2..f90b3fc084e63 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,28 @@ +2015.08.04 Version 3.0.0 + * Added support for SAS to the Azure File service. + * Added support for Append Blob. + * Added support for Access Control Lists (ACL) to File Shares. + * Added support for getting and setting of CORS rules to File service. + * Added support for ShareStats to File Shares. + * Added support for copying an Azure File to another Azure File or a Block Blob asynchronously, and aborting Azure File copy operations asynchronously. + * Added support for copying a Blob to an Azure File asynchronously. + * Added support for setting a maximum quota property on a File Share. + * Removed deprecated AuthenticationScheme and its getter and setter. In the future only SharedKey will be used. + * Removed deprecated getter/setters for all request option properties on the service clients. Please use the default request options getter/setters instead. + * Removed getSubDirectoryReference() for blob directories and file directories. Use getDirectoryReference() instead. + * Removed getEntityClass() in TableQuery. Please use getClazzType() instead. + * Added client-side verification for lease duration and break periods. + * Deprecated the setters in table for timestamp as this property is only modifiable by the service. + * Deprecated startCopyFromBlob() on CloudBlob. Use startCopy() instead. + * Deprecated the Credentials and StorageKey classes. Please use the appropriate methods on StorageCredentialsAccountAndKey instead. + * Deprecated constructors which take service clients in favor of constructors which take credentials. + * Fixed a bug where the DateBackwardCompatibility flag was not applied if set on the CloudTableClient default request options. + * Changed library behavior to retry all exceptions thrown when parsing a response object. + * Changed behavior to stop removing query parameters passed in with the resource URI if that URI contains a SAS token. Some query parameters such as comp, restype, snapshot and api-version will still be removed. + * Added support for logging StringToSign to SharedKey and SAS. + * Added a connect timeout to prevent hangs when establishing the network connection. + * Made performance enhancements to the BlobOutputStream class. + 2015.05.26 Version 2.2.0 * Fixed a bug where maximum execution time was ignored for file, queue, and table services. * Changed the socket timeout to be set to the service side timeout plus 5 minutes when maximum execution time is not set. diff --git a/README.md b/README.md index daf288db05a0f..b29c5053451a4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ #Microsoft Azure Storage SDK for Java -This project provides a client library in Java that makes it easy to consume Microsoft Azure Storage services. For documentation please see the Microsoft Azure [Java Developer Center](http://azure.microsoft.com/en-us/develop/java/) and the [JavaDocs](http://dl.windowsazure.com/storage/javadoc). +This project provides a client library in Java that makes it easy to consume Microsoft Azure Storage services. For documentation please see the Microsoft Azure [Java Developer Center](http://azure.microsoft.com/en-us/develop/java/) and the [JavaDocs](http://azure.github.io/azure-storage-java/). + +> If you are looking for the Azure Storage Android SDK, please visit [https://github.com/Azure/azure-storage-android](https://github.com/Azure/azure-storage-android). #Features * Blob @@ -28,7 +30,7 @@ To get the binaries of this library as distributed by Microsoft, ready for use w com.microsoft.azure azure-storage - 2.2.0 + 3.0.0 ``` @@ -130,4 +132,4 @@ If you encounter any bugs with the library please file an issue in the [Issues]( * [Azure Developer Center](http://azure.microsoft.com/en-us/develop/java/) * [Azure Storage Service](http://azure.microsoft.com/en-us/documentation/services/storage/) * [Azure Storage Team Blog](http://blogs.msdn.com/b/windowsazurestorage/) -* [JavaDocs](http://dl.windowsazure.com/storage/javadoc) +* [JavaDocs](http://azure.github.io/azure-storage-java/) diff --git a/microsoft-azure-storage-samples/pom.xml b/microsoft-azure-storage-samples/pom.xml index 3a053e43af35d..e3ac4ca1319ce 100644 --- a/microsoft-azure-storage-samples/pom.xml +++ b/microsoft-azure-storage-samples/pom.xml @@ -1,31 +1,32 @@ - - 4.0.0 - windowsazure-storage-samples - windowsazure-storage-samples - 0.0.1-SNAPSHOT - - Microsoft Azure Storage Client Samples - Samples for creating blobs, queues and tables - https://github.com/Azure/azure-storage-java - - - src - - - maven-compiler-plugin - 3.0 - - 1.6 - 1.6 - - - - - - - com.microsoft.azure - azure-storage - 2.2.0 - - + + 4.0.0 + com.microsoft.azure + azure-storage-samples + 0.0.1-SNAPSHOT + + Microsoft Azure Storage Client Samples + Samples for creating blobs, queues and tables + https://github.com/Azure/azure-storage-java + + + src + + + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + + + + + + com.microsoft.azure + azure-storage + 3.0.0 + + \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/LoggerTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/LoggerTests.java index f56835c303bb4..e6559ee11af89 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/LoggerTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/LoggerTests.java @@ -20,6 +20,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; import org.junit.AfterClass; import org.junit.Before; @@ -32,6 +34,11 @@ import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.blob.BlobTestHelper; +import com.microsoft.azure.storage.blob.BlobType; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.core.LogConstants; import com.microsoft.azure.storage.core.Logger; /* @@ -268,6 +275,62 @@ public synchronized void testError() throws IOException { readAndCompareOutput(ERROR, OperationContext.defaultLoggerName, ctx.getClientRequestID()); } + @Test + public synchronized void testStringToSign() + throws IOException, InvalidKeyException, StorageException, URISyntaxException { + + OperationContext.setLoggingEnabledByDefault(true); + final CloudBlobContainer cont = BlobTestHelper.getRandomContainerReference(); + + try { + cont.create(); + final CloudBlob blob = BlobTestHelper.uploadNewBlob(cont, BlobType.BLOCK_BLOB, "", 0, null); + + // Test logging for SAS access + baos.reset(); + blob.generateSharedAccessSignature(null, null); + baos.flush(); + + String log = baos.toString(); + String[] logEntries = log.split("[\\r\\n]+"); + + assertEquals(1, logEntries.length); + + // example log entry: TRACE ROOT - {0b902691-1a8e-41da-ab60-5b912df186a6}: {Test string} + String[] segment = logEntries[0].split("\\{"); + assertEquals(3, segment.length); + assertTrue(segment[1].startsWith("*")); + assertTrue(segment[2].startsWith(String.format(LogConstants.SIGNING, Constants.EMPTY_STRING))); + baos.reset(); + + // Test logging for Shared Key access + OperationContext ctx = new OperationContext(); + blob.downloadAttributes(null, null, ctx); + + baos.flush(); + log = baos.toString(); + logEntries = log.split("[\\r\\n]+"); + assertNotEquals(0, logEntries.length); + + // Select correct log entry + for (int n = 0; n < logEntries.length; n++) { + if (logEntries[n].startsWith(LoggerTests.TRACE)) { + segment = logEntries[n].split("\\{"); + assertEquals(3, segment.length); + assertTrue(segment[1].startsWith(ctx.getClientRequestID())); + assertTrue(segment[2].startsWith(String.format(LogConstants.SIGNING, Constants.EMPTY_STRING))); + return; + } + } + + // If this line is reached then the log entry was not found + fail(); + } + finally { + cont.deleteIfExists(); + } + } + private void writeTraceLogs(OperationContext ctx) { Logger.trace(ctx, ARG0); Logger.trace(ctx, ARG1, ARG1_VAL); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/ServicePropertiesTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/ServicePropertiesTests.java index ccbe675f1879b..fe99ac30b056e 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/ServicePropertiesTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/ServicePropertiesTests.java @@ -30,6 +30,8 @@ import com.microsoft.azure.storage.TestRunners.SlowTests; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.file.CloudFileClient; +import com.microsoft.azure.storage.file.FileServiceProperties; import com.microsoft.azure.storage.queue.CloudQueueClient; import com.microsoft.azure.storage.table.CloudTableClient; @@ -360,19 +362,23 @@ public void testCloudValidCorsRules() throws StorageException, InterruptedExcept ServiceClient client = TestHelper.createCloudBlobClient(); ServiceProperties props = new ServiceProperties(); props.setDefaultServiceVersion(Constants.HeaderConstants.TARGET_STORAGE_VERSION); - testCloudValidCorsRules(client, props); + testCloudValidCorsRules(client, props, null); client = TestHelper.createCloudQueueClient(); props = new ServiceProperties(); - testCloudValidCorsRules(client, props); + testCloudValidCorsRules(client, props, null); client = TestHelper.createCloudTableClient(); props = new ServiceProperties(); - testCloudValidCorsRules(client, props); + testCloudValidCorsRules(client, props, null); + + client = TestHelper.createCloudFileClient(); + testCloudValidCorsRules(client, null, new FileServiceProperties()); } - private void testCloudValidCorsRules(ServiceClient client, ServiceProperties props) throws StorageException, - InterruptedException { + private void testCloudValidCorsRules( + ServiceClient client, ServiceProperties props, FileServiceProperties fileServiceProperties) + throws StorageException, InterruptedException { CorsRule ruleMinRequired = new CorsRule(); ruleMinRequired.getAllowedOrigins().add("www.xyz.com"); ruleMinRequired.getAllowedMethods().add(CorsHttpMethods.GET); @@ -416,42 +422,42 @@ private void testCloudValidCorsRules(ServiceClient client, ServiceProperties pro ruleAllowAll.getAllowedHeaders().add("*"); ruleAllowAll.getExposedHeaders().add("*"); - this.testCorsRules(ruleBasic, client, props); + this.testCorsRules(ruleBasic, client, props, fileServiceProperties); - this.testCorsRules(ruleMinRequired, client, props); + this.testCorsRules(ruleMinRequired, client, props, fileServiceProperties); - this.testCorsRules(ruleAllMethods, client, props); + this.testCorsRules(ruleAllMethods, client, props, fileServiceProperties); - this.testCorsRules(ruleSingleExposedHeader, client, props); + this.testCorsRules(ruleSingleExposedHeader, client, props, fileServiceProperties); - this.testCorsRules(ruleSingleExposedPrefixHeader, client, props); + this.testCorsRules(ruleSingleExposedPrefixHeader, client, props, fileServiceProperties); - this.testCorsRules(ruleSingleAllowedHeader, client, props); + this.testCorsRules(ruleSingleAllowedHeader, client, props, fileServiceProperties); - this.testCorsRules(ruleSingleAllowedPrefixHeader, client, props); + this.testCorsRules(ruleSingleAllowedPrefixHeader, client, props, fileServiceProperties); - this.testCorsRules(ruleAllowAll, client, props); + this.testCorsRules(ruleAllowAll, client, props, fileServiceProperties); List testList = new ArrayList(); // Empty rule set should delete all rules - this.testCorsRules(testList, client, props); + this.testCorsRules(testList, client, props, fileServiceProperties); // Test duplicate rules testList.add(ruleBasic); testList.add(ruleBasic); - this.testCorsRules(testList, client, props); + this.testCorsRules(testList, client, props, fileServiceProperties); - // Test max number of rules (five) + // Test max number of rules (five) testList.clear(); testList.add(ruleBasic); testList.add(ruleMinRequired); testList.add(ruleAllMethods); testList.add(ruleSingleExposedHeader); testList.add(ruleSingleExposedPrefixHeader); - this.testCorsRules(testList, client, props); + this.testCorsRules(testList, client, props, fileServiceProperties); - // Test max number of rules (six) + // Test over max number of rules (six) testList.clear(); testList.add(ruleBasic); testList.add(ruleMinRequired); @@ -461,7 +467,7 @@ private void testCloudValidCorsRules(ServiceClient client, ServiceProperties pro testList.add(ruleSingleAllowedHeader); try { - this.testCorsRules(testList, client, props); + this.testCorsRules(testList, client, props, fileServiceProperties); fail("Expecting exception but no exception received. Services are limited to a maximum of five CORS rules."); } catch (StorageException e) { @@ -479,18 +485,22 @@ public void testCorsExpectedExceptions() throws StorageException { ServiceClient client = TestHelper.createCloudBlobClient(); ServiceProperties props = new ServiceProperties(); props.setDefaultServiceVersion(Constants.HeaderConstants.TARGET_STORAGE_VERSION); - testCorsExpectedExceptions(client, props); + testCorsExpectedExceptions(client, props, null); client = TestHelper.createCloudQueueClient(); props = new ServiceProperties(); - testCorsExpectedExceptions(client, props); + testCorsExpectedExceptions(client, props, null); client = TestHelper.createCloudTableClient(); props = new ServiceProperties(); - testCorsExpectedExceptions(client, props); + testCorsExpectedExceptions(client, props, null); + + client = TestHelper.createCloudFileClient(); + testCorsExpectedExceptions(client, null, new FileServiceProperties()); } - private void testCorsExpectedExceptions(ServiceClient client, ServiceProperties props) { + private void testCorsExpectedExceptions( + ServiceClient client, ServiceProperties props, FileServiceProperties fileServiceProperties) { CorsRule ruleEmpty = new CorsRule(); CorsRule ruleInvalidMaxAge = new CorsRule(); @@ -499,7 +509,7 @@ private void testCorsExpectedExceptions(ServiceClient client, ServiceProperties ruleInvalidMaxAge.setMaxAgeInSeconds(-1); try { - this.testCorsRules(ruleEmpty, client, props); + this.testCorsRules(ruleEmpty, client, props, fileServiceProperties); fail("No exception received. A CORS rule must contain at least one allowed origin and allowed method."); } catch (StorageException e) { @@ -513,7 +523,7 @@ private void testCorsExpectedExceptions(ServiceClient client, ServiceProperties } try { - this.testCorsRules(ruleInvalidMaxAge, client, props); + this.testCorsRules(ruleInvalidMaxAge, client, props, fileServiceProperties); fail("No exception received. MaxAgeInSeconds cannot have a value less than 0."); } catch (StorageException e) { @@ -538,19 +548,23 @@ public void testCorsMaxOrigins() throws StorageException, InterruptedException { ServiceClient client = TestHelper.createCloudBlobClient(); ServiceProperties props = new ServiceProperties(); props.setDefaultServiceVersion(Constants.HeaderConstants.TARGET_STORAGE_VERSION); - testCorsMaxOrigins(client, props); + testCorsMaxOrigins(client, props, null); client = TestHelper.createCloudQueueClient(); props = new ServiceProperties(); - testCorsMaxOrigins(client, props); + testCorsMaxOrigins(client, props, null); client = TestHelper.createCloudTableClient(); props = new ServiceProperties(); - testCorsMaxOrigins(client, props); + testCorsMaxOrigins(client, props, null); + + client = TestHelper.createCloudFileClient(); + testCorsMaxOrigins(client, null, new FileServiceProperties()); } - private void testCorsMaxOrigins(ServiceClient client, ServiceProperties props) throws StorageException, - InterruptedException { + private void testCorsMaxOrigins( + ServiceClient client, ServiceProperties props, FileServiceProperties fileServiceProperties) + throws StorageException, InterruptedException { CorsRule ruleManyOrigins = new CorsRule(); ruleManyOrigins.getAllowedMethods().add(CorsHttpMethods.GET); @@ -559,12 +573,12 @@ private void testCorsMaxOrigins(ServiceClient client, ServiceProperties props) t ruleManyOrigins.getAllowedOrigins().add("www.xyz" + i + ".com"); } - this.testCorsRules(ruleManyOrigins, client, props); + this.testCorsRules(ruleManyOrigins, client, props, fileServiceProperties); ruleManyOrigins.getAllowedOrigins().add("www.xyz64.com"); try { - this.testCorsRules(ruleManyOrigins, client, props); + this.testCorsRules(ruleManyOrigins, client, props, fileServiceProperties); fail("No exception received. A maximum of 64 origins are allowed."); } catch (StorageException e) { @@ -585,19 +599,23 @@ public void testCorsMaxHeaders() throws StorageException, InterruptedException { ServiceClient client = TestHelper.createCloudBlobClient(); ServiceProperties props = new ServiceProperties(); props.setDefaultServiceVersion(Constants.HeaderConstants.TARGET_STORAGE_VERSION); - testCorsMaxHeaders(client, props); + testCorsMaxHeaders(client, props, null); client = TestHelper.createCloudQueueClient(); props = new ServiceProperties(); - testCorsMaxHeaders(client, props); + testCorsMaxHeaders(client, props, null); client = TestHelper.createCloudTableClient(); props = new ServiceProperties(); - testCorsMaxHeaders(client, props); + testCorsMaxHeaders(client, props, null); + + client = TestHelper.createCloudFileClient(); + testCorsMaxHeaders(client, null, new FileServiceProperties()); } - private void testCorsMaxHeaders(ServiceClient client, ServiceProperties props) throws StorageException, - InterruptedException { + private void testCorsMaxHeaders( + ServiceClient client, ServiceProperties props, FileServiceProperties fileServiceProperties) + throws StorageException, InterruptedException { CorsRule ruleManyHeaders = new CorsRule(); ruleManyHeaders.getAllowedOrigins().add("www.xyz.com"); ruleManyHeaders.getAllowedMethods().add(CorsHttpMethods.GET); @@ -610,13 +628,13 @@ private void testCorsMaxHeaders(ServiceClient client, ServiceProperties props) t ruleManyHeaders.getExposedHeaders().add("x-ms-meta-" + i); } - this.testCorsRules(ruleManyHeaders, client, props); + this.testCorsRules(ruleManyHeaders, client, props, fileServiceProperties); // Test with too many Exposed Headers (65) ruleManyHeaders.getExposedHeaders().add("x-ms-meta-toomany"); try { - this.testCorsRules(ruleManyHeaders, client, props); + this.testCorsRules(ruleManyHeaders, client, props, fileServiceProperties); fail("No exception received. A maximum of 64 exposed headers are allowed."); } catch (StorageException e) { @@ -631,7 +649,7 @@ private void testCorsMaxHeaders(ServiceClient client, ServiceProperties props) t ruleManyHeaders.getAllowedHeaders().add("x-ms-meta-toomany"); try { - this.testCorsRules(ruleManyHeaders, client, props); + this.testCorsRules(ruleManyHeaders, client, props, fileServiceProperties); fail("No exception received. A maximum of 64 allowed headers are allowed."); } catch (StorageException e) { @@ -646,7 +664,7 @@ private void testCorsMaxHeaders(ServiceClient client, ServiceProperties props) t ruleManyHeaders.getExposedHeaders().add("x-ms-meta-toomany*"); try { - this.testCorsRules(ruleManyHeaders, client, props); + this.testCorsRules(ruleManyHeaders, client, props, fileServiceProperties); fail("No exception received. A maximum of 2 exposed headers are allowed."); } catch (StorageException e) { @@ -661,7 +679,7 @@ private void testCorsMaxHeaders(ServiceClient client, ServiceProperties props) t ruleManyHeaders.getAllowedHeaders().add("x-ms-meta-toomany*"); try { - this.testCorsRules(ruleManyHeaders, client, props); + this.testCorsRules(ruleManyHeaders, client, props, fileServiceProperties); fail("No exception received. A maximum of 64 allowed headers are allowed."); } catch (StorageException e) { @@ -790,37 +808,58 @@ else if (client.getClass().equals(CloudQueueClient.class)) { /** * Takes a CorsRule and tries to upload it. Then tries to download it and compares it to the initial CorsRule. */ - private void testCorsRules(CorsRule rule, ServiceClient client, ServiceProperties props) throws StorageException, - InterruptedException { - props.getCors().getCorsRules().clear(); - props.getCors().getCorsRules().add(rule); - - callUploadServiceProps(client, props); - - assertServicePropertiesAreEqual(props, callDownloadServiceProperties(client)); + private void testCorsRules(CorsRule rule, ServiceClient client, ServiceProperties properties, + FileServiceProperties fileServiceProperties) throws StorageException, InterruptedException { + CorsProperties cors = (fileServiceProperties == null) ? properties.getCors() : fileServiceProperties.getCors(); + cors.getCorsRules().clear(); + cors.getCorsRules().add(rule); + + if (fileServiceProperties == null) { + callUploadServiceProps(client, properties); + assertServicePropertiesAreEqual(properties, callDownloadServiceProperties(client)); + } else { + CloudFileClient fileClient = ((CloudFileClient) client); + fileClient.uploadServiceProperties(fileServiceProperties); + Thread.sleep(30000); + assertFileServicePropertiesAreEqual(fileServiceProperties, fileClient.downloadServiceProperties()); + } } /** * Takes a List of CorsRules and tries to upload them. Then tries to download them and compares the list to the * initial CorsRule List. */ - private void testCorsRules(List corsRules, ServiceClient client, ServiceProperties props) - throws StorageException, InterruptedException { - props.getCors().getCorsRules().clear(); + private void testCorsRules(List corsRules, ServiceClient client, ServiceProperties properties, + FileServiceProperties fileServiceProperties) throws StorageException, InterruptedException { + CorsProperties cors = (fileServiceProperties == null) ? properties.getCors() : fileServiceProperties.getCors(); + cors.getCorsRules().clear(); for (CorsRule rule : corsRules) { - props.getCors().getCorsRules().add(rule); + cors.getCorsRules().add(rule); } - callUploadServiceProps(client, props); - - assertServicePropertiesAreEqual(props, callDownloadServiceProperties(client)); + if (fileServiceProperties == null) { + callUploadServiceProps(client, properties); + assertServicePropertiesAreEqual(properties, callDownloadServiceProperties(client)); + } else { + CloudFileClient fileClient = ((CloudFileClient) client); + fileClient.uploadServiceProperties(fileServiceProperties); + Thread.sleep(30000); + assertFileServicePropertiesAreEqual(fileServiceProperties, fileClient.downloadServiceProperties()); + } } /** * Checks two ServiceProperties for equality */ private static void assertServicePropertiesAreEqual(ServiceProperties propsA, ServiceProperties propsB) { + if (propsA == null && propsB == null) { + return; + } else { + assertNotNull(propsA); + assertNotNull(propsB); + } + if (propsA.getLogging() != null && propsB.getLogging() != null) { assertTrue(propsA.getLogging().getLogOperationTypes().equals(propsB.getLogging().getLogOperationTypes())); assertEquals(propsA.getLogging().getRetentionIntervalInDays(), propsB.getLogging() @@ -889,4 +928,48 @@ private static void assertServicePropertiesAreEqual(ServiceProperties propsA, Se assertNull(propsB.getCors()); } } + + /** + * Checks the CORS rules of two FileServiceProperties objects to ensure they match. + * + * @param original + * The FileServiceProperties used as a point of comparison + * @param target + * The generated FileServiceProperties, which should match the original + */ + private static void assertFileServicePropertiesAreEqual(FileServiceProperties original, FileServiceProperties target) { + if (original == null && target == null) { + return; + } else { + assertNotNull(original); + assertNotNull(target); + } + + if (original.getCors() != null && target.getCors() != null) { + assertEquals(original.getCors().getCorsRules().size(), target.getCors().getCorsRules().size()); + + // Check that rules are equal and in the same order. + for (int i = 0; i < original.getCors().getCorsRules().size(); i++) { + CorsRule ruleOriginal = original.getCors().getCorsRules().get(i); + CorsRule ruleTarget = target.getCors().getCorsRules().get(i); + + assertTrue(ruleOriginal.getAllowedOrigins().size() == ruleTarget.getAllowedOrigins().size()); + assertTrue(ruleOriginal.getAllowedOrigins().containsAll(ruleTarget.getAllowedOrigins())); + + assertTrue(ruleOriginal.getExposedHeaders().size() == ruleTarget.getExposedHeaders().size()); + assertTrue(ruleOriginal.getExposedHeaders().containsAll(ruleTarget.getExposedHeaders())); + + assertTrue(ruleOriginal.getAllowedHeaders().size() == ruleTarget.getAllowedHeaders().size()); + assertTrue(ruleOriginal.getAllowedHeaders().containsAll(ruleTarget.getAllowedHeaders())); + + assertTrue(ruleOriginal.getAllowedMethods().equals(ruleTarget.getAllowedMethods())); + + assertTrue(ruleOriginal.getMaxAgeInSeconds() == ruleTarget.getMaxAgeInSeconds()); + } + } + else { + assertNull(original.getCors()); + assertNull(target.getCors()); + } + } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageAccountTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageAccountTests.java index 9e7c496d96c3e..f2ccccd3bd902 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageAccountTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageAccountTests.java @@ -64,30 +64,31 @@ public void testStorageCredentialsSharedKey() throws URISyntaxException, Storage URI testUri = new URI("http://test/abc?querya=1"); assertEquals(testUri, cred.transformUri(testUri)); - assertEquals(ACCOUNT_KEY, cred.getCredentials().exportBase64EncodedKey()); + assertEquals(ACCOUNT_KEY, cred.exportBase64EncodedKey()); byte[] dummyKey = { 0, 1, 2 }; String base64EncodedDummyKey = Base64.encode(dummyKey); cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, base64EncodedDummyKey); - assertEquals(base64EncodedDummyKey, cred.getCredentials().exportBase64EncodedKey()); + assertEquals(base64EncodedDummyKey, cred.exportBase64EncodedKey()); dummyKey[0] = 3; base64EncodedDummyKey = Base64.encode(dummyKey); cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, base64EncodedDummyKey); - assertEquals(base64EncodedDummyKey, cred.getCredentials().exportBase64EncodedKey()); + assertEquals(base64EncodedDummyKey, cred.exportBase64EncodedKey()); } @Test public void testStorageCredentialsSAS() throws URISyntaxException, StorageException { - String token = "?sig=1&api-version=2014-02-14&sp=abcde"; + String token = "?sig=1&sp=abcde&api-version=" + Constants.HeaderConstants.TARGET_STORAGE_VERSION; StorageCredentialsSharedAccessSignature cred = new StorageCredentialsSharedAccessSignature(token); assertNull(cred.getAccountName()); - URI testUri = new URI("http://test/abc"); - assertEquals(testUri + token, cred.transformUri(testUri).toString()); + URI testUri = new URI("http://test/abc" + token); + TestHelper.assertURIsEqual(testUri, cred.transformUri(testUri), true); testUri = new URI("http://test/abc?query=a&query2=b"); - String expectedUri = "http://test/abc?sig=1&api-version=2014-02-14&query=a&sp=abcde&query2=b"; - assertEquals(expectedUri, cred.transformUri(testUri).toString()); + URI expectedUri = new URI("http://test/abc?sig=1&query=a&sp=abcde&query2=b&api-version=" + + Constants.HeaderConstants.TARGET_STORAGE_VERSION); + TestHelper.assertURIsEqual(expectedUri, cred.transformUri(testUri), true); } @Test @@ -96,36 +97,30 @@ public void testStorageCredentialsEmptyKeyValue() throws URISyntaxException, Inv String emptyKeyConnectionString = String.format(Locale.US, "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=", ACCOUNT_NAME); - StorageCredentialsAccountAndKey credentials1 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, - emptyKeyValueAsString); - assertEquals(ACCOUNT_NAME, credentials1.getAccountName()); - assertEquals(emptyKeyValueAsString, Base64.encode(credentials1.getCredentials().exportKey())); - - CloudStorageAccount account1 = new CloudStorageAccount(credentials1, true); - assertEquals(emptyKeyConnectionString, account1.toString(true)); - assertNotNull(account1.getCredentials()); - assertEquals(ACCOUNT_NAME, account1.getCredentials().getAccountName()); - assertEquals(emptyKeyValueAsString, - Base64.encode(((StorageCredentialsAccountAndKey) (account1.getCredentials())).getCredentials() - .exportKey())); - - CloudStorageAccount account2 = CloudStorageAccount.parse(emptyKeyConnectionString); - assertEquals(emptyKeyConnectionString, account2.toString(true)); - assertNotNull(account2.getCredentials()); - assertEquals(ACCOUNT_NAME, account2.getCredentials().getAccountName()); - assertEquals(emptyKeyValueAsString, - Base64.encode(((StorageCredentialsAccountAndKey) (account2.getCredentials())).getCredentials() - .exportKey())); + try { + new StorageCredentialsAccountAndKey(ACCOUNT_NAME, emptyKeyValueAsString); + fail("Did not hit expected exception"); + } + catch (IllegalArgumentException ex) { + assertEquals(SR.INVALID_KEY, ex.getMessage()); + } - StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); - assertEquals(ACCOUNT_NAME, credentials2.getAccountName()); - assertEquals(ACCOUNT_KEY, Base64.encode(credentials2.getCredentials().exportKey())); + try { + CloudStorageAccount.parse(emptyKeyConnectionString); + fail("Did not hit expected exception"); + } + catch (IllegalArgumentException ex) { + assertEquals(SR.INVALID_CONNECTION_STRING, ex.getMessage()); + } - byte[] emptyKeyValueAsByteArray = new byte[0]; - StorageCredentialsAccountAndKey credentials3 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, - emptyKeyValueAsByteArray); - assertEquals(ACCOUNT_NAME, credentials3.getAccountName()); - assertEquals(Base64.encode(emptyKeyValueAsByteArray), Base64.encode(credentials3.getCredentials().exportKey())); + try { + byte[] emptyKeyValueAsByteArray = new byte[0]; + new StorageCredentialsAccountAndKey(ACCOUNT_NAME, emptyKeyValueAsByteArray); + fail("Did not hit expected exception"); + } + catch (IllegalArgumentException ex) { + assertEquals(SR.INVALID_KEY, ex.getMessage()); + } } @Test @@ -136,13 +131,13 @@ public void testStorageCredentialsNullKeyValue() { new StorageCredentialsAccountAndKey(ACCOUNT_NAME, nullKeyValueAsString); fail("Did not hit expected exception"); } - catch (NullPointerException ex) { - // assertEquals(SR.KEY_NULL, ex.getMessage()); + catch (IllegalArgumentException ex) { + assertEquals(SR.INVALID_KEY, ex.getMessage()); } StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); assertEquals(ACCOUNT_NAME, credentials2.getAccountName()); - assertEquals(ACCOUNT_KEY, Base64.encode(credentials2.getCredentials().exportKey())); + assertEquals(ACCOUNT_KEY, Base64.encode(credentials2.exportKey())); byte[] nullKeyValueAsByteArray = null; try { @@ -150,7 +145,7 @@ public void testStorageCredentialsNullKeyValue() { fail("Did not hit expected exception"); } catch (IllegalArgumentException ex) { - assertEquals(SR.KEY_NULL, ex.getMessage()); + assertEquals(SR.INVALID_KEY, ex.getMessage()); } } @@ -603,10 +598,10 @@ public void testCloudStorageAccountExportKey() throws InvalidKeyException, URISy String accountString = "BlobEndpoint=http://blobs/;AccountName=test;AccountKey=" + accountKeyString; CloudStorageAccount account = CloudStorageAccount.parse(accountString); StorageCredentialsAccountAndKey accountAndKey = (StorageCredentialsAccountAndKey) account.getCredentials(); - String key = accountAndKey.getCredentials().getKey().getBase64EncodedKey(); + String key = accountAndKey.exportBase64EncodedKey(); assertEquals(accountKeyString, key); - byte[] keyBytes = accountAndKey.getCredentials().exportKey(); + byte[] keyBytes = accountAndKey.exportKey(); byte[] expectedKeyBytes = Base64.decode(accountKeyString); assertArrayEquals(expectedKeyBytes, keyBytes); } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageUriTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageUriTests.java index 589e57464d2f4..f067a0c30d8be 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageUriTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/StorageUriTests.java @@ -193,7 +193,7 @@ public void testBlobTypesWithStorageUri() throws StorageException, URISyntaxExce assertEquals(containerUri.getPrimaryUri(), container.getUri()); assertEquals(endpoint, container.getServiceClient().getStorageUri()); - container = new CloudBlobContainer(containerUri, client); + container = new CloudBlobContainer(containerUri, client.getCredentials()); assertEquals(containerUri, container.getStorageUri()); assertEquals(containerUri.getPrimaryUri(), container.getUri()); assertEquals(endpoint, container.getServiceClient().getStorageUri()); @@ -228,7 +228,7 @@ public void testBlobTypesWithStorageUri() throws StorageException, URISyntaxExce assertEquals(containerUri, blockBlob.getContainer().getStorageUri()); assertEquals(endpoint, blockBlob.getServiceClient().getStorageUri()); - blockBlob = new CloudBlockBlob(blobUri, null, client); + blockBlob = new CloudBlockBlob(blobUri, client.getCredentials()); assertEquals(blobUri, blockBlob.getStorageUri()); assertEquals(blobUri.getPrimaryUri(), blockBlob.getUri()); assertEquals(subdirectoryUri, blockBlob.getParent().getStorageUri()); @@ -242,7 +242,7 @@ public void testBlobTypesWithStorageUri() throws StorageException, URISyntaxExce assertEquals(containerUri, pageBlob.getContainer().getStorageUri()); assertEquals(endpoint, pageBlob.getServiceClient().getStorageUri()); - pageBlob = new CloudPageBlob(blobUri, null, client); + pageBlob = new CloudPageBlob(blobUri, client.getCredentials()); assertEquals(blobUri, pageBlob.getStorageUri()); assertEquals(blobUri.getPrimaryUri(), pageBlob.getUri()); assertEquals(subdirectoryUri, pageBlob.getParent().getStorageUri()); @@ -268,7 +268,7 @@ public void testQueueTypesWithStorageUri() throws URISyntaxException, StorageExc assertEquals(queueUri.getPrimaryUri(), queue.getUri()); assertEquals(endpoint, queue.getServiceClient().getStorageUri()); - queue = new CloudQueue(queueUri, client); + queue = new CloudQueue(queueUri, client.getCredentials()); assertEquals(queueUri, queue.getStorageUri()); assertEquals(queueUri.getPrimaryUri(), queue.getUri()); assertEquals(endpoint, queue.getServiceClient().getStorageUri()); @@ -292,7 +292,7 @@ public void testTableTypesWithStorageUri() throws URISyntaxException, StorageExc assertEquals(tableUri.getPrimaryUri(), table.getUri()); assertEquals(endpoint, table.getServiceClient().getStorageUri()); - table = new CloudTable(tableUri, client); + table = new CloudTable(tableUri, client.getCredentials()); assertEquals(tableUri, table.getStorageUri()); assertEquals(tableUri.getPrimaryUri(), table.getUri()); assertEquals(endpoint, table.getServiceClient().getStorageUri()); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java index 8bb80cc859470..3e75ec214e735 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java @@ -14,20 +14,22 @@ */ package com.microsoft.azure.storage; +import static org.junit.Assert.*; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Random; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import junit.framework.Assert; - import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -46,36 +48,26 @@ public class TestHelper { private static StorageCredentialsAccountAndKey credentials; private static CloudStorageAccount account; - @SuppressWarnings("deprecation") - private final static AuthenticationScheme defaultAuthenticationScheme = AuthenticationScheme.SHAREDKEYFULL; private final static boolean enableFiddler = true; private final static boolean requireSecondaryEndpoint = false; - @SuppressWarnings("deprecation") public static CloudBlobClient createCloudBlobClient() throws StorageException { CloudBlobClient client = getAccount().createCloudBlobClient(); - client.setAuthenticationScheme(defaultAuthenticationScheme); return client; } - @SuppressWarnings("deprecation") public static CloudFileClient createCloudFileClient() throws StorageException { CloudFileClient client = getAccount().createCloudFileClient(); - client.setAuthenticationScheme(defaultAuthenticationScheme); return client; } - @SuppressWarnings("deprecation") public static CloudQueueClient createCloudQueueClient() throws StorageException { CloudQueueClient client = getAccount().createCloudQueueClient(); - client.setAuthenticationScheme(defaultAuthenticationScheme); return client; } - @SuppressWarnings("deprecation") public static CloudTableClient createCloudTableClient() throws StorageException { CloudTableClient client = getAccount().createCloudTableClient(); - client.setAuthenticationScheme(defaultAuthenticationScheme); return client; } @@ -103,13 +95,13 @@ public static ByteArrayInputStream getRandomDataStream(int length) { public static void assertStreamsAreEqual(ByteArrayInputStream src, ByteArrayInputStream dst) { dst.reset(); src.reset(); - Assert.assertEquals(src.available(), dst.available()); + assertEquals(src.available(), dst.available()); while (src.available() > 0) { - Assert.assertEquals(src.read(), dst.read()); + assertEquals(src.read(), dst.read()); } - Assert.assertFalse(dst.available() > 0); + assertFalse(dst.available() > 0); } public static void assertStreamsAreEqualAtIndex(ByteArrayInputStream src, ByteArrayInputStream dst, int srcIndex, @@ -125,8 +117,29 @@ public static void assertStreamsAreEqualAtIndex(ByteArrayInputStream src, ByteAr dst.read(destBuffer); for (int i = 0; i < length; i++) { - Assert.assertEquals(src.read(), dst.read()); + assertEquals(src.read(), dst.read()); + } + } + + public static void assertURIsEqual(URI expected, URI actual, boolean ignoreQueryOrder) { + if (expected == null) { + assertEquals(null, actual); } + + assertEquals(expected.getScheme(), actual.getScheme()); + assertEquals(expected.getAuthority(), actual.getAuthority()); + assertEquals(expected.getPath(), actual.getPath()); + assertEquals(expected.getFragment(), actual.getFragment()); + + ArrayList expectedQueries = new ArrayList(Arrays.asList(expected.getQuery().split("&"))); + ArrayList actualQueries = new ArrayList(Arrays.asList(actual.getQuery().split("&"))); + + assertEquals(expectedQueries.size(), actualQueries.size()); + for (String expectedQuery : expectedQueries) { + assertTrue(expectedQuery, actualQueries.remove(expectedQuery)); + } + + assertTrue(actualQueries.isEmpty()); } public static URI defiddler(URI uri) throws URISyntaxException { @@ -143,12 +156,12 @@ public static URI defiddler(URI uri) throws URISyntaxException { } public static void verifyServiceStats(ServiceStats stats) { - Assert.assertNotNull(stats); + assertNotNull(stats); if (stats.getGeoReplication().getLastSyncTime() != null) { - Assert.assertEquals(GeoReplicationStatus.LIVE, stats.getGeoReplication().getStatus()); + assertEquals(GeoReplicationStatus.LIVE, stats.getGeoReplication().getStatus()); } else { - Assert.assertTrue(stats.getGeoReplication().getStatus() == GeoReplicationStatus.BOOTSTRAP + assertTrue(stats.getGeoReplication().getStatus() == GeoReplicationStatus.BOOTSTRAP || stats.getGeoReplication().getStatus() == GeoReplicationStatus.UNAVAILABLE); } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java index c9831a467f714..3fbab8cd2eabc 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java @@ -8,6 +8,8 @@ import org.junit.runners.Suite.SuiteClasses; import com.microsoft.azure.storage.analytics.CloudAnalyticsClientTests; +import com.microsoft.azure.storage.blob.BlobOutputStreamTests; +import com.microsoft.azure.storage.blob.CloudAppendBlobTests; import com.microsoft.azure.storage.blob.CloudBlobClientTests; import com.microsoft.azure.storage.blob.CloudBlobContainerTests; import com.microsoft.azure.storage.blob.CloudBlobDirectoryTests; @@ -19,6 +21,7 @@ import com.microsoft.azure.storage.file.CloudFileDirectoryTests; import com.microsoft.azure.storage.file.CloudFileShareTests; import com.microsoft.azure.storage.file.CloudFileTests; +import com.microsoft.azure.storage.file.FileSasTests; import com.microsoft.azure.storage.queue.CloudQueueClientGB18030Test; import com.microsoft.azure.storage.queue.CloudQueueClientTests; import com.microsoft.azure.storage.queue.CloudQueueTests; @@ -92,8 +95,9 @@ public static class CoreTestSuite { } @RunWith(Suite.class) - @SuiteClasses({ CloudBlobClientTests.class, CloudBlobContainerTests.class, CloudBlobDirectoryTests.class, - CloudBlockBlobTests.class, CloudPageBlobTests.class, LeaseTests.class, SasTests.class }) + @SuiteClasses({ BlobOutputStreamTests.class, CloudBlobClientTests.class, CloudBlobContainerTests.class, + CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class, + LeaseTests.class, SasTests.class }) public static class BlobTestSuite { } @@ -111,7 +115,7 @@ public static class TableTestSuite { @RunWith(Suite.class) @SuiteClasses({ CloudFileClientTests.class, CloudFileDirectoryTests.class, CloudFileShareTests.class, - CloudFileTests.class }) + CloudFileTests.class, FileSasTests.class }) public static class FileTestSuite { } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobOutputStreamTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobOutputStreamTests.java new file mode 100644 index 0000000000000..332cb71753cbb --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobOutputStreamTests.java @@ -0,0 +1,358 @@ +/** + * Copyright 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.azure.storage.blob; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.ResponseReceivedEvent; +import com.microsoft.azure.storage.StorageEvent; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.TestRunners.CloudTests; +import com.microsoft.azure.storage.core.SR; + +/** + * Blob Output Stream Tests + */ +@Category(CloudTests.class) +public class BlobOutputStreamTests { + + protected CloudBlobContainer container; + + @Before + public void blobOutputStreamTestMethodSetup() throws URISyntaxException, StorageException { + this.container = BlobTestHelper.getRandomContainerReference(); + this.container.create(); + } + + @After + public void blobOutputStreamTestMethodTearDown() throws StorageException { + this.container.deleteIfExists(); + } + + @Test + public void testEmpty() throws URISyntaxException, StorageException, IOException { + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); + + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + BlobOutputStream str = blockBlob.openOutputStream(); + str.close(); + + CloudBlockBlob blockBlob2 = this.container.getBlockBlobReference(blobName); + blockBlob2.downloadAttributes(); + assertEquals(0, blockBlob2.getProperties().getLength()); + + ArrayList blocks = blockBlob2.downloadBlockList(BlockListingFilter.ALL, null, null, null); + assertEquals(0, blocks.size()); + } + + @Test + public void testClose() throws URISyntaxException, StorageException, IOException { + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); + + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + BlobOutputStream str = blockBlob.openOutputStream(); + str.close(); + + try { + str.close(); + fail("Can't close twice."); + } catch(IOException e) { + assertEquals(SR.STREAM_CLOSED, e.getMessage()); + } + + str = blockBlob.openOutputStream(); + str.write(8); + ArrayList blocks = blockBlob.downloadBlockList(BlockListingFilter.ALL, null, null, null); + assertEquals(0, blocks.size()); + + str.close(); + blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); + assertEquals(1, blocks.size()); + } + + @Test + public void testWriteStream() throws URISyntaxException, StorageException, IOException { + int blobLengthToUse = 8 * 512; + byte[] buffer = BlobTestHelper.getRandomBuffer(blobLengthToUse); + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); + + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + BlobOutputStream blobOutputStream = blockBlob.openOutputStream(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); + blobOutputStream.write(inputStream, 512); + + inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); + blobOutputStream.write(inputStream, 3 * 512); + + blobOutputStream.close(); + + byte[] result = new byte[blobLengthToUse]; + blockBlob.downloadToByteArray(result, 0); + + int i = 0; + for (; i < 4 * 512; i++) { + assertEquals(buffer[i], result[i]); + } + + for (; i < 8 * 512; i++) { + assertEquals(0, result[i]); + } + } + + @Test + public void testFlush() throws Exception { + CloudBlockBlob blockBlob = this.container.getBlockBlobReference( + BlobTestHelper.generateRandomBlobNameWithPrefix("flush")); + + OperationContext ctx = new OperationContext(); + ctx.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + try { + HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); + if ("511".equals(con.getRequestProperty(Constants.HeaderConstants.CONTENT_LENGTH))) { + Thread.sleep(3000); + } + } catch (InterruptedException e) { + // do nothing + } + } + }); + + BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, null, ctx); + + ExecutorService threadExecutor = Executors.newFixedThreadPool(1); + + byte[] buffer = BlobTestHelper.getRandomBuffer(511); + blobOutputStream.write(buffer); + + Future future = threadExecutor.submit(new FlushTask(blobOutputStream)); + Thread.sleep(1000); + + buffer = BlobTestHelper.getRandomBuffer(513); + blobOutputStream.write(buffer); + + // Writes complete when the upload is dispatched (not when the upload completes and flush must + // wait for upload1 to complete. So, flush should finish last and writes should finish in order. + while(!future.isDone()) { + Thread.sleep(500); + } + + // After flush we should see the first upload + ArrayList blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); + assertEquals(1, blocks.size()); + assertEquals(511, blocks.get(0).getSize()); + + // After close we should see the second upload + blobOutputStream.close(); + blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); + assertEquals(2, blocks.size()); + assertEquals(513, blocks.get(1).getSize()); + } + + @Test + public void testWritesDoubleConcurrency() throws URISyntaxException, StorageException, IOException, + InterruptedException { + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + + // setup the blob output stream with a concurrency of 5 + BlobRequestOptions options = new BlobRequestOptions(); + options.setConcurrentRequestCount(5); + BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); + + // set up the execution completion service + ExecutorService threadExecutor = Executors.newFixedThreadPool(5); + ExecutorCompletionService completion = new ExecutorCompletionService(threadExecutor); + + int tasks = 10; + int writes = 10; + int length = 512; + + // submit tasks to write and flush many blocks + for (int i = 0; i < tasks; i++) { + completion.submit(new WriteTask(blobOutputStream, length, writes, 4 /*flush period*/)); + } + + // wait for all tasks to complete + for (int i = 0; i < tasks; i++) { + completion.take(); + } + + // shut down the thread executor for this method + threadExecutor.shutdown(); + + // check that blocks were committed + ArrayList blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); + assertTrue(blocks.size() != 0); + + // close the stream and check that the blob is the expected length + blobOutputStream.close(); + blockBlob.downloadAttributes(); + assertTrue(blockBlob.getProperties().getLength() == length*writes*tasks); + } + + @Test + public void testWritesNoConcurrency() throws URISyntaxException, StorageException, IOException { + int writes = 10; + + this.smallPutThresholdHelper(Constants.MB, writes, null); + this.writeFlushHelper(512, writes, null, 1); + this.writeFlushHelper(512, writes, null, 4); + this.writeFlushHelper(512, writes, null, writes+1); + } + + @Test + public void testWritesConcurrency() throws URISyntaxException, StorageException, IOException { + int writes = 10; + + BlobRequestOptions options = new BlobRequestOptions(); + options.setConcurrentRequestCount(5); + + this.smallPutThresholdHelper(Constants.MB, writes, options); + this.writeFlushHelper(512, writes, options, 1); + this.writeFlushHelper(512, writes, options, 4); + this.writeFlushHelper(512, writes, options, writes+1); + } + + private void smallPutThresholdHelper(int length, int writes, BlobRequestOptions options) + throws URISyntaxException, StorageException, IOException { + byte[] buffer = BlobTestHelper.getRandomBuffer(length*writes); + + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + blockBlob.setStreamWriteSizeInBytes(length); + + BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); + for (int i = 0; i < writes; i ++) { + blobOutputStream.write(buffer, i*length, length); + } + + blobOutputStream.flush(); + ArrayList blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); + assertEquals(writes, blocks.size()); + + blobOutputStream.close(); + blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); + assertEquals(writes, blocks.size()); + + byte[] outBuffer = new byte[writes*length]; + blockBlob.downloadToByteArray(outBuffer, 0); + for (int i = 0; i < length*writes; i ++) { + assertEquals(buffer[i], outBuffer[i]); + } + } + + private void writeFlushHelper(int length, int writes, BlobRequestOptions options, int flushPeriod) + throws URISyntaxException, StorageException, IOException { + byte[] buffer = BlobTestHelper.getRandomBuffer(length*writes); + + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); + CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); + + ArrayList blocks; + BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); + for (int i = 0; i < writes; i ++) { + blobOutputStream.write(buffer, i*length, length); + + if ((i+1)%flushPeriod == 0) { + blobOutputStream.flush(); + blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); + assertEquals((int)Math.ceil((i+1)/flushPeriod), blocks.size()); + } + } + + blobOutputStream.close(); + blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); + + int flushRequired = writes-flushPeriod < 0 || writes%flushPeriod == 0 ? 0 : 1; + double expected = Math.ceil(((double)writes+flushRequired)/flushPeriod); + assertEquals((long)expected, blocks.size()); + + byte[] outBuffer = new byte[writes*length]; + blockBlob.downloadToByteArray(outBuffer, 0); + for (int i = 0; i < length*writes; i ++) { + assertEquals(buffer[i], outBuffer[i]); + } + } + + private static class FlushTask implements Callable { + final BlobOutputStream stream; + + public FlushTask(BlobOutputStream stream) { + this.stream = stream; + } + + @Override + public Void call() { + try { + stream.flush(); + } catch (IOException e) { + fail("The flush should succeed."); + } + return null; + } + } + + private class WriteTask implements Callable { + final int length; + final int writes; + final int flushPeriod; + final BlobOutputStream blobOutputStream; + + public WriteTask(BlobOutputStream blobOutputStream, int length, int writes, int flushPeriod) { + this.length = length; + this.writes = writes; + this.flushPeriod = flushPeriod; + this.blobOutputStream = blobOutputStream; + } + + @Override + public Void call() { + try { + byte[] buffer = BlobTestHelper.getRandomBuffer(this.length*this.writes); + for (int i = 0; i < writes; i ++) { + this.blobOutputStream.write(buffer, i*this.length, this.length); + + if ((i+1)%flushPeriod == 0) { + this.blobOutputStream.flush(); + } + } + } catch (Exception e) { + fail("flushHelper should succeed."); + } + return null; + } + } +} diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java index f9eb54fff8adf..56499ff2bf793 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java @@ -14,6 +14,8 @@ */ package com.microsoft.azure.storage.blob; +import static org.junit.Assert.*; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; @@ -25,8 +27,6 @@ import java.util.Random; import java.util.UUID; -import junit.framework.Assert; - import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestHelper; @@ -36,7 +36,7 @@ */ public class BlobTestHelper extends TestHelper { - protected static String generateRandomContainerName() { + public static String generateRandomContainerName() { String containerName = "container" + UUID.randomUUID().toString(); return containerName.replace("-", ""); } @@ -49,7 +49,7 @@ public static CloudBlobContainer getRandomContainerReference() throws URISyntaxE return container; } - protected static String generateRandomBlobNameWithPrefix(String prefix) { + public static String generateRandomBlobNameWithPrefix(String prefix) { if (prefix == null) { prefix = ""; } @@ -73,6 +73,12 @@ public static List uploadNewBlobs(CloudBlobContainer container, BlobType blob = uploadNewBlob(container, type, "pb", length, context); blobs.add(blob.getName()); break; + + case APPEND_BLOB: + blob = uploadNewBlob(container, type, "ab", length, context); + blobs.add(blob.getName()); + break; + default: break; @@ -99,6 +105,10 @@ else if (type == BlobType.PAGE_BLOB) { blob = container.getPageBlobReference(name); blob.upload(getRandomDataStream(length), length, null, null, context); } + else if (type == BlobType.APPEND_BLOB) { + blob = container.getAppendBlobReference(name); + blob.upload(getRandomDataStream(length), length, null, null, context); + } return blob; } @@ -117,12 +127,12 @@ protected static void doDownloadTest(CloudBlob blob, int blobSize, int bufferSiz blob.downloadToByteArray(resultBuffer, bufferOffset, null, options, null); for (int i = 0; i < blob.getProperties().getLength(); i++) { - Assert.assertEquals(buffer[i], resultBuffer[bufferOffset + i]); + assertEquals(buffer[i], resultBuffer[bufferOffset + i]); } if (bufferOffset + blobSize < bufferSize) { for (int k = bufferOffset + blobSize; k < bufferSize; k++) { - Assert.assertEquals(0, resultBuffer[k]); + assertEquals(0, resultBuffer[k]); } } } @@ -146,19 +156,19 @@ protected static void doDownloadRangeToByteArrayTest(CloudBlob blob, int blobSiz downloadSize = length.intValue(); } - Assert.assertEquals(downloadSize, downloadLength); + assertEquals(downloadSize, downloadLength); for (int i = 0; i < bufferOffset; i++) { - Assert.assertEquals(0, resultBuffer[i]); + assertEquals(0, resultBuffer[i]); } for (int j = 0; j < downloadLength; j++) { - Assert.assertEquals(buffer[(int) ((blobOffset != null ? blobOffset : 0) + j)], resultBuffer[bufferOffset + assertEquals(buffer[(int) ((blobOffset != null ? blobOffset : 0) + j)], resultBuffer[bufferOffset + j]); } for (int k = bufferOffset + downloadLength; k < bufferSize; k++) { - Assert.assertEquals(0, resultBuffer[k]); + assertEquals(0, resultBuffer[k]); } } @@ -174,22 +184,22 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudBlob blob) th try { blob.downloadRangeToByteArray(1024, (long) 1, resultBuffer, 0); - Assert.fail(); + fail(); } catch (StorageException ex) { - Assert.assertEquals(416, ex.getHttpStatusCode()); + assertEquals(416, ex.getHttpStatusCode()); } try { blob.downloadToByteArray(resultBuffer, 1024); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { } try { blob.downloadRangeToByteArray(0, (long) 1023, resultBuffer, 2); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { @@ -198,7 +208,7 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudBlob blob) th // negative length try { blob.downloadRangeToByteArray(0, (long) -10, resultBuffer, 0); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { @@ -207,7 +217,7 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudBlob blob) th // negative blob offset try { blob.downloadRangeToByteArray(-10, (long) 20, resultBuffer, 0); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { @@ -216,19 +226,33 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudBlob blob) th // negative buffer offset try { blob.downloadRangeToByteArray(0, (long) 20, resultBuffer, -10); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { } } + + public static CloudAppendBlob defiddler(CloudAppendBlob blob) throws URISyntaxException, StorageException { + URI oldUri = blob.getUri(); + URI newUri = defiddler(oldUri); + + if (newUri != oldUri) { + CloudAppendBlob newBlob = new CloudAppendBlob(newUri, blob.getServiceClient().getCredentials()); + newBlob.setSnapshotID(blob.snapshotID); + return newBlob; + } + else { + return blob; + } + } public static CloudBlockBlob defiddler(CloudBlockBlob blob) throws URISyntaxException, StorageException { URI oldUri = blob.getUri(); URI newUri = defiddler(oldUri); if (newUri != oldUri) { - CloudBlockBlob newBlob = new CloudBlockBlob(newUri, blob.getServiceClient()); + CloudBlockBlob newBlob = new CloudBlockBlob(newUri, blob.getServiceClient().getCredentials()); newBlob.setSnapshotID(blob.snapshotID); return newBlob; } @@ -242,7 +266,7 @@ public static CloudPageBlob defiddler(CloudPageBlob blob) throws URISyntaxExcept URI newUri = defiddler(oldUri); if (newUri != oldUri) { - CloudPageBlob newBlob = new CloudPageBlob(newUri, blob.getServiceClient()); + CloudPageBlob newBlob = new CloudPageBlob(newUri, blob.getServiceClient().getCredentials()); newBlob.setSnapshotID(blob.snapshotID); return newBlob; } @@ -254,9 +278,13 @@ public static CloudPageBlob defiddler(CloudPageBlob blob) throws URISyntaxExcept public static void waitForCopy(CloudBlob blob) throws StorageException, InterruptedException { boolean copyInProgress = true; while (copyInProgress) { - Thread.sleep(1000); blob.downloadAttributes(); - copyInProgress = (blob.getCopyState().getStatus() == CopyStatus.PENDING); + copyInProgress = (blob.getCopyState().getStatus() == CopyStatus.PENDING) + || (blob.getCopyState().getStatus() == CopyStatus.ABORTED); + // One second sleep if retry is needed + if (copyInProgress) { + Thread.sleep(1000); + } } } @@ -287,14 +315,14 @@ public static void setBlobProperties(CloudBlob blob) { public static void assertAreEqual(CloudBlob blob1, CloudBlob blob2) throws URISyntaxException, StorageException { if (blob1 == null) { - Assert.assertNull(blob2); + assertNull(blob2); } else { - Assert.assertNotNull(blob2); - Assert.assertEquals(blob1.getUri(), blob2.getUri()); - Assert.assertEquals(blob1.getSnapshotID(), blob2.getSnapshotID()); - Assert.assertEquals(blob1.isSnapshot(), blob2.isSnapshot()); - Assert.assertEquals(blob1.getQualifiedStorageUri(), blob2.getQualifiedStorageUri()); + assertNotNull(blob2); + assertEquals(blob1.getUri(), blob2.getUri()); + assertEquals(blob1.getSnapshotID(), blob2.getSnapshotID()); + assertEquals(blob1.isSnapshot(), blob2.isSnapshot()); + assertEquals(blob1.getQualifiedStorageUri(), blob2.getQualifiedStorageUri()); assertAreEqual(blob1.getProperties(), blob2.getProperties()); assertAreEqual(blob1.getCopyState(), blob2.getCopyState()); } @@ -302,35 +330,35 @@ public static void assertAreEqual(CloudBlob blob1, CloudBlob blob2) throws URISy public static void assertAreEqual(BlobProperties prop1, BlobProperties prop2) { if (prop1 == null) { - Assert.assertNull(prop2); + assertNull(prop2); } else { - Assert.assertNotNull(prop2); - Assert.assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); - Assert.assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); - Assert.assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); - Assert.assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); - Assert.assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); - Assert.assertEquals(prop1.getContentType(), prop2.getContentType()); - Assert.assertEquals(prop1.getEtag(), prop2.getEtag()); - Assert.assertEquals(prop1.getLastModified(), prop2.getLastModified()); - Assert.assertEquals(prop1.getLength(), prop2.getLength()); - Assert.assertEquals(prop1.getPageBlobSequenceNumber(), prop2.getPageBlobSequenceNumber()); + assertNotNull(prop2); + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + assertEquals(prop1.getEtag(), prop2.getEtag()); + assertEquals(prop1.getLastModified(), prop2.getLastModified()); + assertEquals(prop1.getLength(), prop2.getLength()); + assertEquals(prop1.getPageBlobSequenceNumber(), prop2.getPageBlobSequenceNumber()); } } public static void assertAreEqual(CopyState copy1, CopyState copy2) { if (copy1 == null) { - Assert.assertNull(copy2); + assertNull(copy2); } else { - Assert.assertNotNull(copy2); - Assert.assertEquals(copy1.getBytesCopied(), copy2.getBytesCopied()); - Assert.assertEquals(copy1.getCompletionTime(), copy2.getCompletionTime()); - Assert.assertEquals(copy1.getCopyId(), copy2.getCopyId()); - Assert.assertEquals(copy1.getSource(), copy2.getSource()); - Assert.assertEquals(copy1.getStatus(), copy2.getStatus()); - Assert.assertEquals(copy1.getTotalBytes(), copy2.getTotalBytes()); + assertNotNull(copy2); + assertEquals(copy1.getBytesCopied(), copy2.getBytesCopied()); + assertEquals(copy1.getCompletionTime(), copy2.getCompletionTime()); + assertEquals(copy1.getCopyId(), copy2.getCopyId()); + assertEquals(copy1.getSource(), copy2.getSource()); + assertEquals(copy1.getStatus(), copy2.getStatus()); + assertEquals(copy1.getTotalBytes(), copy2.getTotalBytes()); } } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudAppendBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudAppendBlobTests.java new file mode 100644 index 0000000000000..a63449bb84af4 --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudAppendBlobTests.java @@ -0,0 +1,1360 @@ +/** + * Copyright 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.azure.storage.blob; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.Random; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.microsoft.azure.storage.AccessCondition; +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.ResponseReceivedEvent; +import com.microsoft.azure.storage.RetryNoRetry; +import com.microsoft.azure.storage.SendingRequestEvent; +import com.microsoft.azure.storage.StorageErrorCodeStrings; +import com.microsoft.azure.storage.StorageEvent; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.TestRunners.CloudTests; +import com.microsoft.azure.storage.TestRunners.DevFabricTests; +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.Utility; + +@Category({ DevFabricTests.class, CloudTests.class }) +public class CloudAppendBlobTests { + protected CloudBlobContainer container; + + @Before + public void appendBlobTestMethodSetup() throws URISyntaxException, + StorageException { + this.container = BlobTestHelper.getRandomContainerReference(); + this.container.create(); + } + + @After + public void appendBlobTestMethodTearDown() throws StorageException { + this.container.deleteIfExists(); + } + + /** + * Create an append blob. + * + * @throws StorageException + * @throws URISyntaxException + */ + @Test + public void testAppendBlobCreate() throws StorageException, URISyntaxException { + final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlob")); + + assertFalse(blob.exists()); + + // Create + blob.createOrReplace(); + assertTrue(blob.exists()); + + // Create again (should succeed) + blob.createOrReplace(); + assertTrue(blob.exists()); + + // Create again, specifying not to if it already exists + // This should fail + // Add 15 min to account for clock skew + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.MINUTE, 15); + AccessCondition condition = AccessCondition.generateIfModifiedSinceCondition(cal.getTime()); + + try { + blob.createOrReplace(condition, null, null); + fail("Create should fail due to access condition."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage()); + assertEquals("ConditionNotMet", ex.getErrorCode()); + } + + // Create again, specifying not to if it already exists + // This should fail + condition = AccessCondition.generateIfNotExistsCondition(); + try { + blob.createOrReplace(condition, null, null); + fail("Create should fail due to access condition."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode()); + assertEquals("The specified blob already exists.", ex.getMessage()); + assertEquals("BlobAlreadyExists", ex.getErrorCode()); + } + } + + /** + * Delete an append blob. + * + * @throws StorageException + * @throws URISyntaxException + */ + @Test + public void testAppendBlobDelete() throws StorageException, URISyntaxException { + final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlob")); + + assertFalse(blob.exists()); + + // create + blob.createOrReplace(); + assertTrue(blob.exists()); + + // delete + blob.delete(); + assertFalse(blob.exists()); + + // delete again, should fail as it doesn't exist + try { + blob.delete(); + fail("Delete should fail as blob does not exist."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + assertEquals("The specified blob does not exist.", ex.getMessage()); + assertEquals("BlobNotFound", ex.getErrorCode()); + } + } + + /** + * Delete an append blob if it exists. + * + * @throws StorageException + * @throws URISyntaxException + */ + @Test + public void testAppendBlobDeleteIfExists() throws URISyntaxException, StorageException { + final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlob")); + + assertFalse(blob.exists()); + assertFalse(blob.deleteIfExists()); + + blob.createOrReplace(); + assertTrue(blob.exists()); + + assertTrue(blob.deleteIfExists()); + assertFalse(blob.deleteIfExists()); + + // check if second condition works in delete if exists + OperationContext ctx = new OperationContext(); + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + if (((HttpURLConnection) eventArg.getConnectionObject()).getRequestMethod().equals("DELETE")) { + try { + blob.delete(); + assertFalse(blob.exists()); + } + catch (StorageException e) { + fail("Delete should succeed."); + } + } + } + }); + + // The second delete of a blob will return a 404 + blob.createOrReplace(); + assertFalse(blob.deleteIfExists(DeleteSnapshotsOption.NONE, null, null, ctx)); + } + + /** + * Start copying a blob and then abort + */ + @Test + public void testCopyFromAppendBlobAbortTest() throws StorageException, URISyntaxException, IOException { + final int length = 512; + CloudAppendBlob originalBlob = (CloudAppendBlob) BlobTestHelper.uploadNewBlob( + this.container, BlobType.APPEND_BLOB, "originalBlob", length, null); + CloudAppendBlob copyBlob = this.container.getAppendBlobReference(originalBlob.getName() + "copyed"); + copyBlob.startCopy(originalBlob); + + try { + copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId()); + } catch (StorageException e) { + if (!e.getErrorCode().contains("NoPendingCopyOperation")) { + throw e; + } + } + } + + /** + * Create a snapshot + */ + @Test + public void testAppendBlobSnapshotValidationTest() throws StorageException, + URISyntaxException, IOException { + final int length = 1024; + CloudAppendBlob appendBlobRef = (CloudAppendBlob) BlobTestHelper + .uploadNewBlob(this.container, BlobType.APPEND_BLOB, + "originalBlob", length, null); + final CloudBlob blobSnapshot = appendBlobRef.createSnapshot(); + + for (ListBlobItem blob : this.container.listBlobs(null, true, + EnumSet.allOf(BlobListingDetails.class), null, null)) { + final ByteArrayOutputStream outStream = new ByteArrayOutputStream( + length); + ((CloudBlob) blob).download(outStream); + } + + ByteArrayOutputStream outStream = new ByteArrayOutputStream(length); + + blobSnapshot.download(outStream); + byte[] retrievedBuff = outStream.toByteArray(); + assertEquals(length, retrievedBuff.length); + + // Read operation should work fine. + blobSnapshot.downloadAttributes(); + + final CloudAppendBlob blobSnapshotUsingRootUri = this.container + .getAppendBlobReference(appendBlobRef.getName(), + blobSnapshot.getSnapshotID()); + outStream = new ByteArrayOutputStream(length); + + blobSnapshotUsingRootUri.download(outStream); + retrievedBuff = outStream.toByteArray(); + assertEquals(length, retrievedBuff.length); + assertEquals(blobSnapshot.getSnapshotID(), + blobSnapshotUsingRootUri.getSnapshotID()); + + // Expect an IllegalArgumentException from upload. + try { + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + blobSnapshot.upload(new ByteArrayInputStream(buff), -1); + fail("Expect an IllegalArgumentException from upload"); + } catch (IllegalArgumentException e) { + assertEquals( + "Cannot perform this operation on a blob representing a snapshot.", + e.getMessage()); + } + + // Expect an IllegalArgumentException from uploadMetadata. + try { + blobSnapshot.uploadMetadata(); + fail("Expect an IllegalArgumentException from uploadMetadata"); + } catch (IllegalArgumentException e) { + assertEquals( + "Cannot perform this operation on a blob representing a snapshot.", + e.getMessage()); + } + + // Expect an IllegalArgumentException from uploadProperties. + try { + blobSnapshot.uploadProperties(); + fail("Expect an IllegalArgumentException from uploadProperties"); + } catch (IllegalArgumentException e) { + assertEquals( + "Cannot perform this operation on a blob representing a snapshot.", + e.getMessage()); + } + + // Expect an IllegalArgumentException from createSnapshot. + try { + blobSnapshot.createSnapshot(); + fail("Expect an IllegalArgumentException from createSnapshot"); + } catch (IllegalArgumentException e) { + assertEquals( + "Cannot perform this operation on a blob representing a snapshot.", + e.getMessage()); + } + } + + /** + * Create a blob and try to download a range of its contents + */ + @Test + public void testAppendBlobDownloadRangeValidationTest() + throws StorageException, URISyntaxException, IOException { + final int length = 5 * 1024 * 1024; + + final String appendBlobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlockBlob"); + final CloudAppendBlob appendBlobRef = this.container + .getAppendBlobReference(appendBlobName); + + appendBlobRef + .upload(BlobTestHelper.getRandomDataStream(length), length); + + // Download full blob + appendBlobRef.download(new ByteArrayOutputStream()); + assertEquals(length, appendBlobRef.getProperties().getLength()); + + // Download blob range. + byte[] downloadBuffer = new byte[100]; + int downloadLength = appendBlobRef.downloadRangeToByteArray(0, + (long) 100, downloadBuffer, 0); + assertEquals(length, appendBlobRef.getProperties().getLength()); + assertEquals(100, downloadLength); + } + + @Test + public void testAppendBlobUploadFromStreamTest() throws URISyntaxException, + StorageException, IOException { + final String appendBlobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testAppendBlob"); + final CloudAppendBlob appendBlobRef = this.container + .getAppendBlobReference(appendBlobName); + + int length = 2 * 1024; + ByteArrayInputStream srcStream = BlobTestHelper + .getRandomDataStream(length); + appendBlobRef.upload(srcStream, length); + ByteArrayOutputStream dstStream = new ByteArrayOutputStream(); + appendBlobRef.download(dstStream); + BlobTestHelper.assertStreamsAreEqual(srcStream, + new ByteArrayInputStream(dstStream.toByteArray())); + + length = 5 * 1024 * 1024; + srcStream = BlobTestHelper.getRandomDataStream(length); + appendBlobRef.upload(srcStream, length); + dstStream = new ByteArrayOutputStream(); + appendBlobRef.download(dstStream); + BlobTestHelper.assertStreamsAreEqual(srcStream, + new ByteArrayInputStream(dstStream.toByteArray())); + } + + @Test + public void testBlobUploadWithoutMD5Validation() throws URISyntaxException, + StorageException, IOException { + final String appendBlobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testAppendBlob"); + final CloudAppendBlob appendBlobRef = this.container + .getAppendBlobReference(appendBlobName); + + final int length = 2 * 1024; + ByteArrayInputStream srcStream = BlobTestHelper + .getRandomDataStream(length); + BlobRequestOptions options = new BlobRequestOptions(); + options.setDisableContentMD5Validation(false); + options.setStoreBlobContentMD5(false); + + appendBlobRef.upload(srcStream, length, null, options, null); + appendBlobRef.downloadAttributes(); + appendBlobRef.getProperties().setContentMD5("MDAwMDAwMDA="); + appendBlobRef.uploadProperties(null, options, null); + + try { + appendBlobRef.download(new ByteArrayOutputStream(), null, options, + null); + fail(); + } catch (StorageException ex) { + assertEquals(306, ex.getHttpStatusCode()); + assertEquals("InvalidMd5", ex.getErrorCode()); + } + + options.setDisableContentMD5Validation(true); + appendBlobRef + .download(new ByteArrayOutputStream(), null, options, null); + } + + @Test + public void testBlobEmptyHeaderSigningTest() throws URISyntaxException, + StorageException, IOException { + final String appendBlobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testAppendBlob"); + final CloudAppendBlob appendBlobRef = this.container + .getAppendBlobReference(appendBlobName); + + final int length = 2 * 1024; + ByteArrayInputStream srcStream = BlobTestHelper + .getRandomDataStream(length); + + OperationContext context = new OperationContext(); + context.getSendingRequestEventHandler().addListener( + new StorageEvent() { + + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection connection = (HttpURLConnection) eventArg + .getConnectionObject(); + connection.setRequestProperty("x-ms-foo", ""); + } + }); + + appendBlobRef.upload(srcStream, length, null, null, context); + appendBlobRef + .download(new ByteArrayOutputStream(), null, null, context); + } + + @Test + public void testAppendBlobDownloadRangeTest() throws URISyntaxException, + StorageException, IOException { + byte[] buffer = BlobTestHelper.getRandomBuffer(2 * 1024); + + CloudAppendBlob blob = this.container.getAppendBlobReference("blob1"); + ByteArrayInputStream wholeBlob = new ByteArrayInputStream(buffer); + BlobRequestOptions opt = new BlobRequestOptions(); + opt.setStoreBlobContentMD5(false); + blob.upload(wholeBlob, 2 * 1024, null, opt, null); + + ByteArrayOutputStream blobStream = new ByteArrayOutputStream(); + try { + blob.downloadRange(0, new Long(0), blobStream); + } catch (IndexOutOfBoundsException ex) { + + } + + blob.downloadRange(0, new Long(1024), blobStream); + assertEquals(blobStream.size(), 1024); + BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream( + blobStream.toByteArray()), wholeBlob, 0, 0, 1024, 2 * 1024); + + CloudAppendBlob blob2 = this.container.getAppendBlobReference("blob1"); + try { + blob.downloadRange(1024, new Long(0), blobStream); + } catch (IndexOutOfBoundsException ex) { + + } + + ByteArrayOutputStream blobStream2 = new ByteArrayOutputStream(); + blob2.downloadRange(1024, new Long(1024), blobStream2); + BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream( + blobStream2.toByteArray()), wholeBlob, 1024, 1024, 1024, + 2 * 1024); + + BlobTestHelper.assertAreEqual(blob, blob2); + } + + @Test + public void testCloudAppendBlobDownloadToByteArray() + throws URISyntaxException, StorageException, IOException { + CloudAppendBlob blob = this.container.getAppendBlobReference("blob1"); + BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 0); + BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 1 * 512); + BlobTestHelper.doDownloadTest(blob, 2 * 512, 4 * 512, 1 * 512); + BlobTestHelper + .doDownloadTest(blob, 5 * 1024 * 1024, 5 * 1024 * 1024, 0); + BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 6 * 1024 * 1024, + 512); + } + + @Test + public void testCloudAppendBlobDownloadRangeToByteArray() + throws URISyntaxException, StorageException, IOException { + CloudAppendBlob blob = this.container + .getAppendBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("downloadrange")); + + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, + 8 * 1024 * 1024, 1 * 1024 * 1024, new Long(1 * 1024 * 1024), + new Long(5 * 1024 * 1024)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, + 8 * 1024 * 1024, 2 * 1024 * 1024, new Long(2 * 1024 * 1024), + new Long(6 * 1024 * 1024)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, + 8 * 1024 * 1024, 1 * 1024 * 1024, new Long(4 * 1024 * 1024), + new Long(4 * 1024 * 1024)); + + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 0, new Long(1 * 512), new Long(1 * 512)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 1 * 512, new Long(0), null); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 1 * 512, new Long(1 * 512), null); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 1 * 512, new Long(0), new Long(1 * 512)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 2 * 512, new Long(1 * 512), new Long(1 * 512)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, + 2 * 512, new Long(1 * 512), new Long(2 * 512)); + + // Edge cases + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 1023, + new Long(1023), new Long(1)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, + new Long(1023), new Long(1)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, + new Long(0), new Long(1)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, + new Long(512), new Long(1)); + BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 512, + new Long(1023), new Long(1)); + } + + @Test + public void testCloudAppendBlobDownloadRangeToByteArrayNegativeTest() + throws URISyntaxException, StorageException, IOException { + CloudAppendBlob blob = this.container + .getAppendBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("downloadrangenegative")); + BlobTestHelper.doDownloadRangeToByteArrayNegativeTests(blob); + } + + @Test + public void testCloudAppendBlobUploadFromStreamWithAccessCondition() + throws URISyntaxException, StorageException, IOException { + CloudAppendBlob blob1 = this.container.getAppendBlobReference("blob1"); + AccessCondition accessCondition = AccessCondition + .generateIfNoneMatchCondition("\"*\""); + final int length = 6 * 512; + ByteArrayInputStream srcStream = BlobTestHelper + .getRandomDataStream(length); + blob1.upload(srcStream, length, accessCondition, null, null); + + srcStream.reset(); + blob1.createOrReplace(); + accessCondition = AccessCondition.generateIfNoneMatchCondition(blob1 + .getProperties().getEtag()); + try { + blob1.upload(srcStream, length, accessCondition, null, null); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, + ex.getHttpStatusCode()); + } + + srcStream.reset(); + accessCondition = AccessCondition.generateIfMatchCondition(blob1 + .getProperties().getEtag()); + blob1.upload(srcStream, length, accessCondition, null, null); + + srcStream.reset(); + CloudAppendBlob blob2 = this.container.getAppendBlobReference("blob2"); + blob2.createOrReplace(); + accessCondition = AccessCondition.generateIfMatchCondition(blob1 + .getProperties().getEtag()); + try { + blob1.upload(srcStream, length, accessCondition, null, null); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, + ex.getHttpStatusCode()); + } + + srcStream.reset(); + accessCondition = AccessCondition.generateIfNoneMatchCondition(blob2 + .getProperties().getEtag()); + blob1.upload(srcStream, length, accessCondition, null, null); + } + + @Test + public void testAppendBlobNamePlusEncodingTest() + throws StorageException, URISyntaxException, IOException, InterruptedException { + final int length = 1 * 1024; + + final CloudAppendBlob originalBlob = (CloudAppendBlob) BlobTestHelper.uploadNewBlob( + this.container, BlobType.APPEND_BLOB, "a+b.txt", length, null); + final CloudAppendBlob copyBlob = this.container.getAppendBlobReference(originalBlob.getName() + "copyed"); + + copyBlob.startCopy(originalBlob); + BlobTestHelper.waitForCopy(copyBlob); + copyBlob.downloadAttributes(); + } + + @Test + public void testAppendBlobInputStream() throws URISyntaxException, + StorageException, IOException { + final int blobLength = 16 * 1024; + final Random randGenerator = new Random(); + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blobRef = this.container + .getAppendBlobReference(blobName); + + final byte[] buff = new byte[blobLength]; + randGenerator.nextBytes(buff); + buff[0] = -1; + buff[1] = -128; + final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff); + + final BlobRequestOptions options = new BlobRequestOptions(); + final OperationContext operationContext = new OperationContext(); + options.setTimeoutIntervalInMs(90000); + options.setRetryPolicyFactory(new RetryNoRetry()); + blobRef.upload(sourceStream, blobLength, null, options, + operationContext); + + BlobInputStream blobStream = blobRef.openInputStream(); + + for (int i = 0; i < blobLength; i++) { + int data = blobStream.read(); + assertTrue(data >= 0); + assertEquals(buff[i], (byte) data); + } + + assertEquals(-1, blobStream.read()); + + blobRef.delete(); + } + + @Test + public void testAppendBlobUploadNegativeLength() throws URISyntaxException, + StorageException, IOException { + final int blobLength = 16 * 1024; + + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blobRef = this.container + .getAppendBlobReference(blobName); + + final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength); + ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff); + + blobRef.upload(sourceStream, -1); + + assertTrue(blobRef.exists()); + assertEquals(blobRef.getProperties().getLength(), blobLength); + } + + @Test + public void testAppendBlobMaxSizeCondition() throws URISyntaxException, + StorageException, IOException { + final int blobLength = 16 * 1024; + + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blobRef = this.container + .getAppendBlobReference(blobName); + + final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength); + ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff); + + // Max length position failure + AccessCondition cond = new AccessCondition(); + cond.setIfMaxSizeLessThanOrEqual(blobLength - 1L); + + try { + blobRef.upload(sourceStream, blobLength, cond, null, null); + fail("Expected IOException for exceeding the max size"); + } catch (IOException ex) { + assertEquals(SR.INVALID_BLOCK_SIZE, ex.getMessage()); + } + } + + @Test + public void testAppendBlobWriteStreamConditionalRetry() throws URISyntaxException, + StorageException, IOException { + final int blobLength = 16 * 1024; + + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blobRef = this.container + .getAppendBlobReference(blobName); + + final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength); + + final BlobRequestOptions options = new BlobRequestOptions(); + options.setAbsorbConditionalErrorsOnRetry(true); + + // Append position failure + OperationContext ctx = new OperationContext(); + ctx.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + int count = 0; + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + // This is the first try at appending. This error code will cause it to retry. + // On the retry we set the error to append pos which should be ignored as the + // absorb conditional errors on retry flag is set to true. + if (count == 1) { + eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR); + } else if (count == 2) { + eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_PRECON_FAILED); + eventArg.getRequestResult().setStatusMessage(StorageErrorCodeStrings.INVALID_APPEND_POSITION); + } + count++; + } + }); + + ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff); + blobRef.upload(sourceStream, blobLength, null, options, ctx); + + // Max length position failure + AccessCondition cond = new AccessCondition(); + cond.setIfMaxSizeLessThanOrEqual((long)blobLength); + + ctx = new OperationContext(); + ctx.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + int count = 0; + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + // This is the first try at appending. This error code will cause it to retry. + // On the retry we set the error to max size which should be ignored as the + // absorb conditional errors on retry flag is set to true. + if (count == 1) { + eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR); + } else if (count == 2) { + eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_PRECON_FAILED); + eventArg.getRequestResult().setStatusMessage(StorageErrorCodeStrings.INVALID_MAX_BLOB_SIZE_CONDITION); + } + count++; + } + }); + + sourceStream = new ByteArrayInputStream(buff); + blobRef.upload(sourceStream, blobLength, cond, options, ctx); + } + + @Test + public void testUploadFromByteArray() throws Exception { + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blob = this.container + .getAppendBlobReference(blobName); + + this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512); + this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512); + this.doUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512); + this.doUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512); + } + + @Test + public void testUploadDownloadFromFile() throws IOException, + StorageException, URISyntaxException { + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blob = this.container + .getAppendBlobReference(blobName); + + this.doUploadDownloadFileTest(blob, 512); + this.doUploadDownloadFileTest(blob, 4096); + this.doUploadDownloadFileTest(blob, 5 * 1024 * 1024); + this.doUploadDownloadFileTest(blob, 11 * 1024 * 1024); + } + + @Test + public void testAppendBlobCopyTest() + throws URISyntaxException, StorageException, InterruptedException, IOException { + Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); + + CloudAppendBlob source = this.container + .getAppendBlobReference("source"); + + byte[] buffer = BlobTestHelper.getRandomBuffer(512); + ByteArrayInputStream stream = new ByteArrayInputStream(buffer); + source.upload(stream, buffer.length); + source.getMetadata().put("Test", "value"); + source.uploadMetadata(); + + CloudAppendBlob copy = this.container.getAppendBlobReference("copy"); + String copyId = copy.startCopy(BlobTestHelper.defiddler(source)); + BlobTestHelper.waitForCopy(copy); + + assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); + assertEquals(source.getQualifiedUri().getPath(), copy.getCopyState() + .getSource().getPath()); + assertEquals(buffer.length, copy.getCopyState().getTotalBytes() + .intValue()); + assertEquals(buffer.length, copy.getCopyState().getBytesCopied() + .intValue()); + assertEquals(copyId, copy.getCopyState().getCopyId()); + assertTrue(copy.getCopyState().getCompletionTime() + .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0); + + try { + copy.abortCopy(copy.getCopyState().getCopyId()); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_CONFLICT, + ex.getHttpStatusCode()); + } + + source.downloadAttributes(); + assertNotNull(copy.getProperties().getEtag()); + assertFalse(source.getProperties().getEtag() + .equals(copy.getProperties().getEtag())); + assertTrue(copy.getProperties().getLastModified() + .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0); + + ByteArrayOutputStream copyStream = new ByteArrayOutputStream(); + copy.download(copyStream); + BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream( + copyStream.toByteArray())); + + copy.downloadAttributes(); + BlobProperties prop1 = copy.getProperties(); + BlobProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), + prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value", copy.getMetadata().get("Test")); + + copy.delete(); + } + + @Test + public void testAppendBlobCopyWithMetadataOverride() + throws URISyntaxException, StorageException, IOException, InterruptedException { + Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); + CloudAppendBlob source = this.container + .getAppendBlobReference("source"); + + byte[] buffer = BlobTestHelper.getRandomBuffer(512); + ByteArrayInputStream stream = new ByteArrayInputStream(buffer); + + source.upload(stream, buffer.length); + + source.getMetadata().put("Test", "value"); + source.uploadMetadata(); + + CloudAppendBlob copy = this.container.getAppendBlobReference("copy"); + copy.getMetadata().put("Test2", "value2"); + String copyId = copy.startCopy(BlobTestHelper.defiddler(source)); + BlobTestHelper.waitForCopy(copy); + + assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); + assertEquals(source.getQualifiedUri().getPath(), copy.getCopyState() + .getSource().getPath()); + assertEquals(buffer.length, copy.getCopyState().getTotalBytes() + .intValue()); + assertEquals(buffer.length, copy.getCopyState().getBytesCopied() + .intValue()); + assertEquals(copyId, copy.getCopyState().getCopyId()); + assertTrue(copy.getCopyState().getCompletionTime() + .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0); + + ByteArrayOutputStream copyStream = new ByteArrayOutputStream(); + copy.download(copyStream); + BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream( + copyStream.toByteArray())); + + copy.downloadAttributes(); + source.downloadAttributes(); + BlobProperties prop1 = copy.getProperties(); + BlobProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), + prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value2", copy.getMetadata().get("Test2")); + assertFalse(copy.getMetadata().containsKey("Test")); + + copy.delete(); + } + + @Test + public void testAppendBlobCopyFromSnapshot() + throws StorageException, IOException, URISyntaxException, InterruptedException { + CloudAppendBlob source = this.container + .getAppendBlobReference("source"); + + byte[] buffer = BlobTestHelper.getRandomBuffer(512); + ByteArrayInputStream stream = new ByteArrayInputStream(buffer); + + source.upload(stream, buffer.length); + + source.getMetadata().put("Test", "value"); + source.uploadMetadata(); + + CloudAppendBlob snapshot = (CloudAppendBlob) source.createSnapshot(); + + // Modify source + byte[] buffer2 = BlobTestHelper.getRandomBuffer(512); + ByteArrayInputStream stream2 = new ByteArrayInputStream(buffer2); + source.getMetadata().put("Test", "newvalue"); + source.uploadMetadata(); + source.getProperties().setContentMD5(null); + source.upload(stream2, buffer.length); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + source.download(outputStream); + + ByteArrayOutputStream snapshotStream = new ByteArrayOutputStream(); + snapshot.download(snapshotStream); + BlobTestHelper.assertStreamsAreEqual(stream2, new ByteArrayInputStream( + outputStream.toByteArray())); + BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream( + snapshotStream.toByteArray())); + + source.downloadAttributes(); + snapshot.downloadAttributes(); + assertFalse(source.getMetadata().get("Test") + .equals(snapshot.getMetadata().get("Test"))); + + CloudAppendBlob copy = this.container.getAppendBlobReference("copy"); + String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot)); + BlobTestHelper.waitForCopy(copy); + + ByteArrayOutputStream copyStream = new ByteArrayOutputStream(); + copy.download(copyStream); + + assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); + BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream( + copyStream.toByteArray())); + assertEquals(copyId, copy.getProperties().getCopyState().getCopyId()); + + copy.downloadAttributes(); + BlobProperties prop1 = copy.getProperties(); + BlobProperties prop2 = snapshot.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), + prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value", copy.getMetadata().get("Test")); + + copy.delete(); + } + + private void doUploadFromByteArrayTest(CloudAppendBlob blob, + int bufferSize, int bufferOffset, int count) throws Exception { + byte[] buffer = BlobTestHelper.getRandomBuffer(bufferSize); + byte[] downloadedBuffer = new byte[bufferSize]; + + blob.uploadFromByteArray(buffer, bufferOffset, count); + blob.downloadToByteArray(downloadedBuffer, 0); + + int i = 0; + for (; i < count; i++) { + assertEquals(buffer[i + bufferOffset], downloadedBuffer[i]); + } + + for (; i < downloadedBuffer.length; i++) { + assertEquals(0, downloadedBuffer[i]); + } + } + + private void doUploadDownloadFileTest(CloudAppendBlob blob, int fileSize) + throws IOException, StorageException { + File sourceFile = File.createTempFile("sourceFile", ".tmp"); + File destinationFile = new File(sourceFile.getParentFile(), + "destinationFile.tmp"); + + try { + + byte[] buffer = BlobTestHelper.getRandomBuffer(fileSize); + FileOutputStream fos = new FileOutputStream(sourceFile); + fos.write(buffer); + fos.close(); + blob.uploadFromFile(sourceFile.getAbsolutePath()); + + blob.downloadToFile(destinationFile.getAbsolutePath()); + assertTrue("Destination file does not exist.", + destinationFile.exists()); + assertEquals("Destination file does not match input file.", + fileSize, destinationFile.length()); + FileInputStream fis = new FileInputStream(destinationFile); + + byte[] readBuffer = new byte[fileSize]; + fis.read(readBuffer); + fis.close(); + + for (int i = 0; i < fileSize; i++) { + assertEquals("File contents do not match.", buffer[i], + readBuffer[i]); + } + } finally { + if (sourceFile.exists()) { + sourceFile.delete(); + } + + if (destinationFile.exists()) { + destinationFile.delete(); + } + } + } + + @Test + public void testUploadDownloadBlobProperties() throws URISyntaxException, + StorageException, IOException { + final int length = 512; + + // do this to make sure the set MD5 can be compared without an exception + // being thrown + BlobRequestOptions options = new BlobRequestOptions(); + options.setDisableContentMD5Validation(true); + + // with explicit upload/download of properties + String appendBlobName1 = BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlockBlob"); + CloudAppendBlob appendBlobRef1 = this.container + .getAppendBlobReference(appendBlobName1); + + appendBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), + length); + + // this is not set by upload (it is for page blob!), so set this + // manually + appendBlobRef1.getProperties().setLength(length); + + BlobTestHelper.setBlobProperties(appendBlobRef1); + BlobProperties props1 = appendBlobRef1.getProperties(); + appendBlobRef1.uploadProperties(); + + appendBlobRef1.downloadAttributes(null, options, null); + BlobProperties props2 = appendBlobRef1.getProperties(); + + BlobTestHelper.assertAreEqual(props1, props2); + + // by uploading/downloading the blob + appendBlobName1 = BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlockBlob"); + appendBlobRef1 = this.container.getAppendBlobReference(appendBlobName1); + + BlobTestHelper.setBlobProperties(appendBlobRef1); + props1 = appendBlobRef1.getProperties(); + + appendBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), + length); + + // this is not set by upload (it is for page blob!), so set this + // manually + appendBlobRef1.getProperties().setLength(length); + + appendBlobRef1.download(new ByteArrayOutputStream(), null, options, + null); + props2 = appendBlobRef1.getProperties(); + + BlobTestHelper.assertAreEqual(props1, props2); + } + + @Test + public void testOpenOutputStream() throws URISyntaxException, + StorageException, IOException { + int blobLengthToUse = 8 * 512; + byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512); + + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + final CloudAppendBlob blobRef = this.container + .getAppendBlobReference(blobName); + blobRef.createOrReplace(); + + BlobOutputStream blobOutputStream = blobRef.openWriteNew(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); + + blobOutputStream = blobRef.openWriteNew(); + inputStream = new ByteArrayInputStream(buffer); + blobOutputStream.write(inputStream, 512); + + inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); + blobOutputStream.write(inputStream, 3 * 512); + + blobOutputStream.close(); + + byte[] result = new byte[blobLengthToUse]; + blobRef.downloadToByteArray(result, 0); + + int i = 0; + for (; i < 4 * 512; i++) { + assertEquals(buffer[i], result[i]); + } + + for (; i < 8 * 512; i++) { + assertEquals(0, result[i]); + } + } + + @Test + public void testOpenOutputStreamNoArgs() throws URISyntaxException, + StorageException { + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + CloudAppendBlob appendBlob = this.container + .getAppendBlobReference(blobName); + + try { + appendBlob.openWriteExisting(); + } catch (StorageException ex) { + assertEquals("The specified blob does not exist.", ex.getMessage()); + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, + ex.getHttpStatusCode()); + } + + appendBlob.openWriteNew(); + appendBlob.openWriteExisting(); + + CloudAppendBlob appendBlob2 = this.container + .getAppendBlobReference(blobName); + appendBlob2.downloadAttributes(); + assertEquals(0, appendBlob2.getProperties().getLength()); + assertEquals(BlobType.APPEND_BLOB, appendBlob2.getProperties().getBlobType()); + } + + @Test + public void testOpenOutputStreamWithConditions() throws StorageException, IOException, URISyntaxException + { + int blobSize = 1024; + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + CloudAppendBlob blob = container.getAppendBlobReference(blobName); + + BlobOutputStream str = blob.openWriteNew(); + byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize); + str.write(buffer); + str.close(); + + // Succeeded max size condition + AccessCondition accessCondition = new AccessCondition(); + accessCondition.setIfMaxSizeLessThanOrEqual(1024*2+1L); + str = blob.openWriteExisting(accessCondition, null, null); + str.write(buffer); + str.close(); + + // Succeeded append position condition + accessCondition = new AccessCondition(); + accessCondition.setIfAppendPositionEqual(1024*2L); + str = blob.openWriteExisting(accessCondition, null, null); + str.write(buffer); + str.close(); + + // Failed max size condition + accessCondition = new AccessCondition(); + accessCondition.setIfMaxSizeLessThanOrEqual(1024 - 1L); + try { + str = blob.openWriteExisting(accessCondition, null, null); + str.write(buffer); + str.close(); + fail("Expected a condition failure."); + } catch (IOException ex) { + assertEquals(SR.INVALID_BLOCK_SIZE, ex.getMessage()); + } + + // Failed append position condition + accessCondition = new AccessCondition(); + accessCondition.setIfAppendPositionEqual(1024 - 1L); + try { + str = blob.openWriteExisting(accessCondition, null, null); + str.write(buffer); + str.close(); + fail("Expected a condition failure."); + } catch (IOException ex) { + StorageException internalException = (StorageException)ex.getCause(); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, internalException.getHttpStatusCode()); + assertEquals("The append position condition specified was not met.", internalException.getMessage()); + assertEquals("AppendPositionConditionNotMet", internalException.getErrorCode()); + } + } + + @Test + public void testOpenOutputStreamMultiWriterFail() throws StorageException, + IOException, URISyntaxException { + int blobSize = 1024; + byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize); + + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + CloudAppendBlob blob = container.getAppendBlobReference(blobName); + blob.createOrReplace(); + final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, blobSize); + + // Create two streams to the same blob + BlobOutputStream str = blob.openWriteExisting(); + BlobOutputStream str2 = blob.openWriteExisting(); + + // These will write to an internal buffer and not throw as they do + // not yet make a service call + str.write(buffer); + str2.write(buffer); + + // Append the data from the first stream (service call happens) + str.close(); + + // Failed append position condition + try { + // Append the data from the second stream which expects append + // position to be 1024 from the intial download properties when + // the stream was opened + str2.close(); + fail("Expected a condition failure."); + } catch (IOException ex) { + StorageException internalException = (StorageException) ex + .getCause(); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, + internalException.getHttpStatusCode()); + assertEquals( + "The append position condition specified was not met.", + internalException.getMessage()); + assertEquals("AppendPositionConditionNotMet", + internalException.getErrorCode()); + } + } + + @Test + public void testAppendBlockFromStream() throws StorageException, IOException, URISyntaxException + { + int blobSize = 2 * 1024; + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + CloudAppendBlob blob = container.getAppendBlobReference(blobName); + blob.createOrReplace(); + + // Append a block + byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize); + ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer); + long pos = blob.appendBlock(sourceStream, -1); + assertEquals(0, pos); + + // Download and verify equality + byte[] resultBuffer = new byte[blobSize]; + blob.downloadToByteArray(resultBuffer, 0); + for (int i = 0; i < blob.getProperties().getLength(); i++) { + assertEquals(buffer[i], resultBuffer[i]); + } + + // Append another block to check the position is updated correctly + sourceStream = new ByteArrayInputStream(buffer); + pos = blob.appendBlock(sourceStream, -1); + assertEquals(blobSize, pos); + } + + @Test + public void testAppendBlockFromStreamWithConditions() throws StorageException, IOException, URISyntaxException + { + int blobSize = 1024; + String blobName = BlobTestHelper + .generateRandomBlobNameWithPrefix("testblob"); + CloudAppendBlob blob = this.container.getAppendBlobReference(blobName); + blob.createOrReplace(); + + byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize); + ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, -1); + + // Succeeded max size condition + AccessCondition accessCondition = new AccessCondition(); + accessCondition.setIfMaxSizeLessThanOrEqual(1024*2+1L); + sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, -1, accessCondition, null, null); + + // Succeeded append position condition + accessCondition = new AccessCondition(); + accessCondition.setIfAppendPositionEqual(1024*2L); + sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, -1, accessCondition, null, null); + + // Failed max size condition + accessCondition = new AccessCondition(); + accessCondition.setIfMaxSizeLessThanOrEqual(1024 - 1L); + try { + sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, -1, accessCondition, null, null); + fail("Expected a condition failure."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, + ex.getHttpStatusCode()); + assertEquals("The max blob size condition specified was not met.", ex.getMessage()); + assertEquals("MaxBlobSizeConditionNotMet", ex.getErrorCode()); + } + + // Failed append position condition + accessCondition = new AccessCondition(); + accessCondition.setIfAppendPositionEqual(1024 - 1L); + try { + sourceStream = new ByteArrayInputStream(buffer); + blob.appendBlock(sourceStream, -1, accessCondition, null, null); + fail("Expected a condition failure."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, + ex.getHttpStatusCode()); + assertEquals("The append position condition specified was not met.", ex.getMessage()); + assertEquals("AppendPositionConditionNotMet", ex.getErrorCode()); + } + } + + @Test + public void testAppendBlobUploadFromStream() throws StorageException, IOException, URISyntaxException + { + byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024); + + CloudAppendBlob blob = this.container.getAppendBlobReference("blob1"); + + blob.upload(new ByteArrayInputStream(buffer), buffer.length); + blob.downloadAttributes(); + assertEquals(6 * 1024 * 1024, blob.getProperties().getLength()); + + blob.upload(new ByteArrayInputStream(buffer), buffer.length); + blob.downloadAttributes(); + assertEquals(6 * 1024 * 1024, blob.getProperties().getLength()); + + blob.upload(new ByteArrayInputStream(buffer), buffer.length, null /* accessCondition */, null /* options */, + null /* operationContext */); + blob.downloadAttributes(); + assertEquals(6 * 1024 * 1024, blob.getProperties().getLength()); + } + + @Test + public void testAppendBlobAppendFromStream() throws StorageException, IOException, URISyntaxException + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024); + + CloudAppendBlob blob = this.container.getAppendBlobReference("blob1"); + blob.createOrReplace(); + + blob.append(new ByteArrayInputStream(buffer), buffer.length); + blob.downloadAttributes(); + assertEquals(6 * 1024 * 1024, blob.getProperties().getLength()); + + blob.append(new ByteArrayInputStream(buffer), buffer.length); + blob.downloadAttributes(); + assertEquals(12 * 1024 * 1024, blob.getProperties().getLength()); + + blob.append(new ByteArrayInputStream(buffer), buffer.length, null /* accessCondition */, null /* options */, + null /* operationContext */); + blob.downloadAttributes(); + assertEquals(18 * 1024 * 1024, blob.getProperties().getLength()); + } + + @Test + public void testAppendBlobAppendFromStreamWithLength() throws StorageException, IOException, URISyntaxException + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024); + + CloudAppendBlob blob = this.container.getAppendBlobReference("blob1"); + blob.createOrReplace(); + + blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024); + blob.downloadAttributes(); + assertEquals(5 * 1024 * 1024, blob.getProperties().getLength()); + + blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024); + blob.downloadAttributes(); + assertEquals(10 * 1024 * 1024, blob.getProperties().getLength()); + + blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024, null /* accessCondition */, null /* options */, + null /* operationContext */); + blob.downloadAttributes(); + assertEquals(15 * 1024 * 1024, blob.getProperties().getLength()); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java index f938632e4ae56..7bf483c9ebfa0 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java @@ -242,8 +242,8 @@ public void testCloudBlobContainerExists() throws StorageException { */ @Test @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class }) - public void testCloudBlobContainerSetPermissions() throws StorageException, InterruptedException, - URISyntaxException { + public void testCloudBlobContainerSetPermissions() + throws StorageException, InterruptedException, URISyntaxException { CloudBlobClient client = BlobTestHelper.createCloudBlobClient(); this.container.create(); @@ -251,12 +251,10 @@ public void testCloudBlobContainerSetPermissions() throws StorageException, Inte assertTrue(BlobContainerPublicAccessType.OFF.equals(permissions.getPublicAccess())); assertEquals(0, permissions.getSharedAccessPolicies().size()); - Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - cal = new GregorianCalendar(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE), - cal.get(Calendar.HOUR), cal.get(Calendar.MINUTE)); - Date start = cal.getTime(); + final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + final Date start = cal.getTime(); cal.add(Calendar.MINUTE, 30); - Date expiry = cal.getTime(); + final Date expiry = cal.getTime(); permissions.setPublicAccess(BlobContainerPublicAccessType.CONTAINER); SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); @@ -288,14 +286,7 @@ public void testCloudBlobContainerSetPermissions() throws StorageException, Inte @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testCloudBlobContainerPermissionsFromString() { - Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - Date start = cal.getTime(); - cal.add(Calendar.MINUTE, 30); - Date expiry = cal.getTime(); - SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); - policy.setSharedAccessStartTime(start); - policy.setSharedAccessExpiryTime(expiry); policy.setPermissionsFromString("rwdl"); assertEquals(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE, @@ -319,14 +310,7 @@ public void testCloudBlobContainerPermissionsFromString() { @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testCloudBlobContainerPermissionsToString() { - Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - Date start = cal.getTime(); - cal.add(Calendar.MINUTE, 30); - Date expiry = cal.getTime(); - SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); - policy.setSharedAccessStartTime(start); - policy.setSharedAccessExpiryTime(expiry); policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST)); @@ -545,12 +529,12 @@ public void testCloudBlobContainerListBlobsOptions() throws StorageException, IO // leased blob CloudBlockBlob leasedBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "originalBlobLeased", length, null); - leasedBlob.acquireLease(null, null); + leasedBlob.acquireLease(); // copy of regular blob CloudBlockBlob copyBlob = this.container.getBlockBlobReference(BlobTestHelper .generateRandomBlobNameWithPrefix("originalBlobCopy")); - copyBlob.startCopyFromBlob(originalBlob); + copyBlob.startCopy(originalBlob); BlobTestHelper.waitForCopy(copyBlob); // snapshot of regular blob @@ -559,7 +543,7 @@ public void testCloudBlobContainerListBlobsOptions() throws StorageException, IO // snapshot of the copy of the regular blob CloudBlockBlob copySnapshot = this.container.getBlockBlobReference(BlobTestHelper .generateRandomBlobNameWithPrefix("originalBlobSnapshotCopy")); - copySnapshot.startCopyFromBlob(copyBlob); + copySnapshot.startCopy(copyBlob); BlobTestHelper.waitForCopy(copySnapshot); int count = 0; diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobDirectoryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobDirectoryTests.java index 80bcbe647fa38..2b58e6c5f6374 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobDirectoryTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobDirectoryTests.java @@ -376,8 +376,7 @@ private void testFlatListingWithDirectory(String delimiter, CloudBlobContainer c null), get22.getUri()); } - // @Test - // Re-enable after fix in 3.0 with int->Integer + @Test public void testFlatListingWithDirectorySegmented() throws URISyntaxException, StorageException { for (int i = 0; i < delimiters.length; i++) { CloudBlobContainer container = null; @@ -399,7 +398,7 @@ private void testFlatListingWithDirectorySegmented(String delimiter, CloudBlobCo ArrayList list1 = new ArrayList(); do { ResultSegment result1 = directory.listBlobsSegmented(null, false, - EnumSet.noneOf(BlobListingDetails.class), -1, token, null, null); + EnumSet.noneOf(BlobListingDetails.class), null, token, null, null); token = result1.getContinuationToken(); list1.addAll(result1.getResults()); } while (token != null); @@ -424,7 +423,7 @@ private void testFlatListingWithDirectorySegmented(String delimiter, CloudBlobCo ArrayList list2 = new ArrayList(); do { ResultSegment result1 = midDir2.listBlobsSegmented(null, true, - EnumSet.noneOf(BlobListingDetails.class), -1, token, null, null); + EnumSet.noneOf(BlobListingDetails.class), null, token, null, null); token = result1.getContinuationToken(); list2.addAll(result1.getResults()); } while (token != null); @@ -850,4 +849,4 @@ private static CloudBlobContainer createContainer(String delimiter) throws URISy container.create(); return container; } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java index 2ba503a5341dd..6c9b7bb779174 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java @@ -36,8 +36,6 @@ import java.util.Random; import java.util.TimeZone; -import junit.framework.Assert; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -58,6 +56,12 @@ import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.TestRunners.SlowTests; import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.file.CloudFile; +import com.microsoft.azure.storage.file.CloudFileShare; +import com.microsoft.azure.storage.file.FileProperties; +import com.microsoft.azure.storage.file.FileTestHelper; +import com.microsoft.azure.storage.file.SharedAccessFilePermissions; +import com.microsoft.azure.storage.file.SharedAccessFilePolicy; /** * Block Blob Tests @@ -78,6 +82,82 @@ public void blockBlobTestMethodTearDown() throws StorageException { this.container.deleteIfExists(); } + /** + * Create a block blob. + * + * @throws StorageException + * @throws URISyntaxException + * @throws IOException + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testBlockBlobCreate() throws StorageException, URISyntaxException, IOException { + final CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlob")); + + assertFalse(blob.exists()); + + // Create + blob.uploadText("text"); + assertTrue(blob.exists()); + + // Create again (should succeed) + blob.uploadText("text"); + assertTrue(blob.exists()); + + // Create again, specifying not to if it already exists + // This should fail + // Add 15 min to account for clock skew + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.MINUTE, 15); + AccessCondition condition = new AccessCondition(); + condition.setIfModifiedSinceDate(cal.getTime()); + + try { + blob.uploadText("text", null, condition, null, null); + fail("Create should fail do to access condition."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage()); + assertEquals("ConditionNotMet", ex.getErrorCode()); + } + } + + /** + * Delete a block blob. + * + * @throws StorageException + * @throws URISyntaxException + * @throws IOException + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testBlockBlobDelete() throws StorageException, URISyntaxException, IOException { + final CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper + .generateRandomBlobNameWithPrefix("testBlob")); + + assertFalse(blob.exists()); + + // create + blob.uploadText("text"); + assertTrue(blob.exists()); + + // delete + blob.delete(); + assertFalse(blob.exists()); + + // delete again, should fail as it doesn't exist + try { + blob.delete(); + fail("Delete should fail as blob does not exist."); + } catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + assertEquals("The specified blob does not exist.", ex.getMessage()); + assertEquals("BlobNotFound", ex.getErrorCode()); + } + } + /** * Test blob name validation. */ @@ -188,12 +268,12 @@ public void eventOccurred(SendingRequestEvent eventArg) { } }); - copyDestination.startCopyFromBlob(copySource.getUri(), null, null, null, ctx); - copyDestination.startCopyFromBlob(copySource, null, null, null, ctx); + copyDestination.startCopy(copySource.getUri(), null, null, null, ctx); + copyDestination.startCopy(copySource, null, null, null, ctx); } @Test - @Category({ DevFabricTests.class, DevStoreTests.class }) + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) public void testCopyBlockBlobWithMetadataOverride() throws URISyntaxException, StorageException, IOException, InterruptedException { Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); @@ -209,7 +289,7 @@ public void testCopyBlockBlobWithMetadataOverride() throws URISyntaxException, S CloudBlockBlob copy = this.container.getBlockBlobReference("copy"); copy.getMetadata().put("Test2", "value2"); - String copyId = copy.startCopyFromBlob(BlobTestHelper.defiddler(source)); + String copyId = copy.startCopy(BlobTestHelper.defiddler(source)); BlobTestHelper.waitForCopy(copy); assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); @@ -241,7 +321,7 @@ public void testCopyBlockBlobWithMetadataOverride() throws URISyntaxException, S } @Test - @Category({ DevFabricTests.class, DevStoreTests.class }) + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) public void testCopyBlockBlobFromSnapshot() throws StorageException, IOException, URISyntaxException, InterruptedException { CloudBlockBlob source = this.container.getBlockBlobReference("source"); @@ -271,7 +351,7 @@ public void testCopyBlockBlobFromSnapshot() throws StorageException, IOException assertFalse(source.getMetadata().get("Test").equals(snapshot.getMetadata().get("Test"))); CloudBlockBlob copy = this.container.getBlockBlobReference("copy"); - String copyId = copy.startCopyFromBlob(BlobTestHelper.defiddler(snapshot)); + String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot)); BlobTestHelper.waitForCopy(copy); copy.downloadAttributes(); @@ -308,10 +388,10 @@ public void testCopyBlockBlobFromSnapshot() throws StorageException, IOException @Category({ DevFabricTests.class, DevStoreTests.class }) public void testCopyFromBlobAbortTest() throws StorageException, URISyntaxException, IOException { final int length = 128; - CloudBlob originalBlob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "originalBlob", - length, null); - CloudBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed"); - copyBlob.startCopyFromBlob(originalBlob); + CloudBlockBlob originalBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob( + this.container, BlobType.BLOCK_BLOB, "originalBlob", length, null); + CloudBlockBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed"); + copyBlob.startCopy(originalBlob); try { copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId()); @@ -323,9 +403,202 @@ public void testCopyFromBlobAbortTest() throws StorageException, URISyntaxExcept } } + + @Test + @Category(SlowTests.class) + public void testCopyFileSas() + throws InvalidKeyException, URISyntaxException, StorageException, IOException, InterruptedException { + // Create source on server. + final CloudFileShare share = FileTestHelper.getRandomShareReference(); + try { + share.create(); + final CloudFile source = share.getRootDirectoryReference().getFileReference("source"); + + final String data = "String data"; + source.getMetadata().put("Test", "value"); + source.uploadText(data, Constants.UTF8_CHARSET, null, null, null); + + Calendar cal = Calendar.getInstance(Utility.UTC_ZONE); + cal.add(Calendar.MINUTE, 5); + + // Source SAS must have read permissions + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ)); + policy.setSharedAccessExpiryTime(cal.getTime()); + + String sasToken = source.generateSharedAccessSignature(policy, null, null); + + // Get destination reference + final CloudBlockBlob destination = this.container.getBlockBlobReference("destination"); + + // Start copy and wait for completion + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + String copyId = destination.startCopy(new CloudFile(credentials.transformUri(source.getUri()))); + BlobTestHelper.waitForCopy(destination); + destination.downloadAttributes(); + assertNotNull(destination.getProperties().getEtag()); + + // Check original file references for equality + assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); + assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), + destination.getCopyState().getSource().getPath()); + assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue()); + assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue()); + assertEquals(copyId, destination.getProperties().getCopyState().getCopyId()); + + // Attempt to abort the completed copy operation. + try { + destination.abortCopy(destination.getCopyState().getCopyId()); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode()); + } + + String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null); + assertEquals(data, copyData); + + source.downloadAttributes(); + BlobProperties prop1 = destination.getProperties(); + FileProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value", destination.getMetadata().get("Test")); + assertEquals(1, destination.getMetadata().size()); + } + finally { + share.deleteIfExists(); + } + } + + @Test + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) + public void testCopyFileWithMetadataOverride() + throws URISyntaxException, StorageException, IOException, InterruptedException, InvalidKeyException { + Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); + String data = "String data"; + + final CloudFileShare share = FileTestHelper.getRandomShareReference(); + try { + share.create(); + final CloudFile source = share.getRootDirectoryReference().getFileReference("source"); + FileTestHelper.setFileProperties(source); + + // do this to make sure the set MD5 can be compared, otherwise when the dummy value + // doesn't match the actual MD5 an exception would be thrown + BlobRequestOptions options = new BlobRequestOptions(); + options.setDisableContentMD5Validation(true); + + source.getMetadata().put("Test", "value"); + source.uploadText(data); + + calendar.add(Calendar.MINUTE, 5); + + // Source SAS must have read permissions + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ)); + policy.setSharedAccessExpiryTime(calendar.getTime()); + + String sasToken = source.generateSharedAccessSignature(policy, null, null); + + // Get source BlockBlob reference + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + CloudBlockBlob destination = this.container.getBlockBlobReference("copy"); + + destination.getMetadata().put("Test2", "value2"); + String copyId = destination.startCopy( + FileTestHelper.defiddler(new CloudFile(credentials.transformUri(source.getUri())))); + BlobTestHelper.waitForCopy(destination); + destination.downloadAttributes(); + + assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); + assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), + destination.getCopyState().getSource().getPath()); + assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue()); + assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue()); + assertEquals(copyId, destination.getCopyState().getCopyId()); + assertTrue(0 < destination.getCopyState().getCompletionTime().compareTo( + new Date(calendar.get(Calendar.MINUTE) - 6))); + + String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, options, null); + assertEquals(data, copyData); + + source.downloadAttributes(); + BlobProperties prop1 = destination.getProperties(); + FileProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), + prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value2", destination.getMetadata().get("Test2")); + assertFalse(destination.getMetadata().containsKey("Test")); + assertEquals(1, destination.getMetadata().size()); + } + finally { + share.deleteIfExists(); + } + } + + /** + * Start copying a file and then abort + * + * @throws StorageException + * @throws URISyntaxException + * @throws IOException + * @throws InvalidKeyException + * @throws InterruptedException + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCopyFileAbort() + throws StorageException, URISyntaxException, IOException, InvalidKeyException, InterruptedException { + final int length = 128; + final CloudFileShare share = FileTestHelper.getRandomShareReference(); + share.create(); + final CloudFile source = FileTestHelper.uploadNewFile(share, length, null); + + // Source SAS must have read permissions + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ)); + + Calendar cal = Calendar.getInstance(Utility.UTC_ZONE); + cal.add(Calendar.MINUTE, 5); + policy.setSharedAccessExpiryTime(cal.getTime()); + String sasToken = source.generateSharedAccessSignature(policy, null, null); + + // Start copy and wait for completion + final CloudBlockBlob destination = this.container.getBlockBlobReference(source.getName() + "copyed"); + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + destination.startCopy(new CloudFile(credentials.transformUri(source.getUri()))); + + try { + destination.abortCopy(destination.getProperties().getCopyState().getCopyId()); + BlobTestHelper.waitForCopy(destination); + fail(); + } + catch (StorageException e) { + if (!e.getErrorCode().contains("NoPendingCopyOperation")) { + throw e; + } + } + finally { + share.deleteIfExists(); + } + } + @Test @Category({ DevFabricTests.class, DevStoreTests.class }) - public void deleteBlobIfExists() throws URISyntaxException, StorageException, IOException { + public void testDeleteBlobIfExists() throws URISyntaxException, StorageException, IOException { final CloudBlockBlob blob1 = this.container.getBlockBlobReference(BlobTestHelper .generateRandomBlobNameWithPrefix("testBlob")); @@ -563,6 +836,9 @@ public void testDownloadBlockList() throws URISyntaxException, StorageException, assertFalse(extraBlocks.remove(blockItem.getId()) == null); } assertEquals(0, extraBlocks.size()); + + blockList = blob2.downloadBlockList(BlockListingFilter.ALL, null, null, null); + assertEquals(5, blockList.size()); } @Test @@ -677,7 +953,7 @@ public void testUploadDownloadBlobProperties() throws URISyntaxException, Storag blockBlobRef1.downloadAttributes(null, options, null); BlobProperties props2 = blockBlobRef1.getProperties(); - Assert.assertEquals(props1.getLength(), props2.getLength()); + assertEquals(props1.getLength(), props2.getLength()); BlobTestHelper.assertAreEqual(props1, props2); // by uploading/downloading the blob @@ -899,16 +1175,16 @@ public void testCloudBlockBlobUploadFromStreamWithAccessCondition() throws URISy * @throws InterruptedException */ @Test - @Category({ DevFabricTests.class, DevStoreTests.class }) + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) public void testBlobNamePlusEncodingTest() throws StorageException, URISyntaxException, IOException, InterruptedException { final int length = 1 * 1024; final CloudBlockBlob originalBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "a+b.txt", length, null); - final CloudBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed"); + final CloudBlockBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed"); - copyBlob.startCopyFromBlob(originalBlob); + copyBlob.startCopy(originalBlob); BlobTestHelper.waitForCopy(copyBlob); copyBlob.downloadAttributes(); } @@ -996,43 +1272,6 @@ public void testBlobInputStream() throws URISyntaxException, StorageException, I blobRef.delete(); } - @Test - public void testBlobOutputStream() throws URISyntaxException, StorageException, IOException { - int blobLengthToUse = 8 * 512; - byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512); - String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); - - CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); - BlobOutputStream str = blockBlob.openOutputStream(); - str.close(); - - CloudBlockBlob blockBlob2 = this.container.getBlockBlobReference(blobName); - blockBlob2.downloadAttributes(); - assertEquals(0, blockBlob2.getProperties().getLength()); - assertEquals(BlobType.BLOCK_BLOB, blockBlob2.getProperties().getBlobType()); - - BlobOutputStream blobOutputStream = blockBlob.openOutputStream(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); - blobOutputStream.write(inputStream, 512); - - inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); - blobOutputStream.write(inputStream, 3 * 512); - - blobOutputStream.close(); - - byte[] result = new byte[blobLengthToUse]; - blockBlob.downloadToByteArray(result, 0); - - int i = 0; - for (; i < 4 * 512; i++) { - assertEquals(buffer[i], result[i]); - } - - for (; i < 8 * 512; i++) { - assertEquals(0, result[i]); - } - } - @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testUploadFromByteArray() throws Exception { @@ -1344,7 +1583,7 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) source.getMetadata().put("Test", "value"); source.uploadMetadata(); - // Create destination on server. + // Get destination reference CloudBlockBlob destination = this.container.getBlockBlobReference("destination"); destination.commitBlockList(new ArrayList()); @@ -1368,7 +1607,7 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) String sasToken = source.generateSharedAccessSignature(policy, null); - // Get source + // Get source BlockBlob reference StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); copySource = new CloudBlockBlob(credentials.transformUri(source.getUri())); } @@ -1390,7 +1629,7 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) String sasToken = destination.generateSharedAccessSignature(policy, null); - // Get destination + // Get destination block blob reference StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); copyDestination = new CloudBlockBlob(credentials.transformUri(destination.getUri())); } @@ -1398,7 +1637,7 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) Thread.sleep(30000); // Start copy and wait for completion - String copyId = copyDestination.startCopyFromBlob(copySource); + String copyId = copyDestination.startCopy(copySource); BlobTestHelper.waitForCopy(copyDestination); Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); destination.downloadAttributes(); @@ -1429,7 +1668,6 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null); assertEquals(data, copyData); - destination.downloadAttributes(); BlobProperties prop1 = destination.getProperties(); BlobProperties prop2 = source.getProperties(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java index 6e4bb201ed224..824509a302095 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java @@ -30,8 +30,6 @@ import java.util.EnumSet; import java.util.Random; -import junit.framework.Assert; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -75,10 +73,10 @@ public void pageBlobTestMethodTearDown() throws StorageException { @Test public void testCopyFromPageBlobAbortTest() throws StorageException, URISyntaxException, IOException { final int length = 512; - CloudBlob originalBlob = BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB, "originalBlob", - length, null); - CloudBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed"); - copyBlob.startCopyFromBlob(originalBlob); + CloudPageBlob originalBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob( + this.container, BlobType.PAGE_BLOB, "originalBlob", length, null); + CloudPageBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed"); + copyBlob.startCopy(originalBlob); try { copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId()); @@ -368,7 +366,7 @@ public void testCloudPageBlobUploadFromStreamWithAccessCondition() throws URISyn blob1.upload(srcStream, length, accessCondition, null, null); } catch (StorageException ex) { - Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); } srcStream.reset(); @@ -383,7 +381,7 @@ public void testCloudPageBlobUploadFromStreamWithAccessCondition() throws URISyn blob1.upload(srcStream, length, accessCondition, null, null); } catch (StorageException ex) { - Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); } srcStream.reset(); @@ -404,9 +402,9 @@ public void testPageBlobNamePlusEncodingTest() throws StorageException, URISynta final CloudPageBlob originalBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB, "a+b.txt", length, null); - final CloudBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed"); + final CloudPageBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed"); - copyBlob.startCopyFromBlob(originalBlob); + copyBlob.startCopy(originalBlob); BlobTestHelper.waitForCopy(copyBlob); copyBlob.downloadAttributes(); } @@ -483,7 +481,7 @@ public void testPageBlobCopyTest() throws URISyntaxException, StorageException, source.uploadMetadata(); CloudPageBlob copy = this.container.getPageBlobReference("copy"); - String copyId = copy.startCopyFromBlob(BlobTestHelper.defiddler(source)); + String copyId = copy.startCopy(BlobTestHelper.defiddler(source)); BlobTestHelper.waitForCopy(copy); assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); @@ -541,7 +539,7 @@ public void testPageBlobCopyWithMetadataOverride() throws URISyntaxException, St CloudPageBlob copy = this.container.getPageBlobReference("copy"); copy.getMetadata().put("Test2", "value2"); - String copyId = copy.startCopyFromBlob(BlobTestHelper.defiddler(source)); + String copyId = copy.startCopy(BlobTestHelper.defiddler(source)); BlobTestHelper.waitForCopy(copy); assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); @@ -609,7 +607,7 @@ public void testPageBlobCopyFromSnapshot() throws StorageException, IOException, assertFalse(source.getMetadata().get("Test").equals(snapshot.getMetadata().get("Test"))); CloudPageBlob copy = this.container.getPageBlobReference("copy"); - String copyId = copy.startCopyFromBlob(BlobTestHelper.defiddler(snapshot)); + String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot)); BlobTestHelper.waitForCopy(copy); ByteArrayOutputStream copyStream = new ByteArrayOutputStream(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/LeaseTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/LeaseTests.java index 9a1b770e8c984..d553097e50afe 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/LeaseTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/LeaseTests.java @@ -51,6 +51,23 @@ public void leaseTestMethodSetup() throws StorageException, URISyntaxException { public void leaseTestMethodTearDown() throws StorageException { this.container.deleteIfExists(); } + + @Test + public void testContainerLeaseInvalidParams() throws StorageException, URISyntaxException { + try { + this.container.acquireLease(100, null); + } catch(StorageException ex) { + assertEquals("The value of the parameter 'leaseTimeInSeconds' should be between 15 and 60.", + ex.getMessage()); + } + + try { + this.container.breakLease(100); + } catch(StorageException ex) { + assertEquals("The value of the parameter 'breakPeriodInSeconds' should be between 0 and 60.", + ex.getMessage()); + } + } @Test public void testContainerAcquireLease() throws StorageException, URISyntaxException { @@ -79,7 +96,6 @@ public void testContainerAcquireLease() throws StorageException, URISyntaxExcept leaseId2 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2); assertEquals(leaseId1, leaseId2); - } finally { // cleanup @@ -107,8 +123,7 @@ public void testContainerReleaseLease() throws StorageException { assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); // infinite - proposedLeaseId = UUID.randomUUID().toString(); - leaseId = this.container.acquireLease(null /* infinite lease */, proposedLeaseId); + leaseId = this.container.acquireLease(); condition = new AccessCondition(); condition.setLeaseID(leaseId); OperationContext operationContext2 = new OperationContext(); @@ -122,19 +137,18 @@ public void testContainerBreakLease() throws StorageException, InterruptedExcept String proposedLeaseId = UUID.randomUUID().toString(); try { // 5 sec - String leaseId = this.container.acquireLease(15, proposedLeaseId); + this.container.acquireLease(15, proposedLeaseId); AccessCondition condition = new AccessCondition(); - condition.setLeaseID(leaseId); + condition.setLeaseID(proposedLeaseId); OperationContext operationContext1 = new OperationContext(); this.container.breakLease(0, condition, null/* BlobRequestOptions */, operationContext1); assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); Thread.sleep(15 * 1000); // infinite - proposedLeaseId = UUID.randomUUID().toString(); - leaseId = this.container.acquireLease(null /* infinite lease */, proposedLeaseId); + proposedLeaseId = this.container.acquireLease(); condition = new AccessCondition(); - condition.setLeaseID(leaseId); + condition.setLeaseID(proposedLeaseId); OperationContext operationContext2 = new OperationContext(); this.container.breakLease(0, condition, null/* BlobRequestOptions */, operationContext2); assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); @@ -152,19 +166,18 @@ public void testContainerRenewLeaseTest() throws StorageException { String proposedLeaseId = UUID.randomUUID().toString(); try { // 5 sec - String leaseId = this.container.acquireLease(15, proposedLeaseId); + this.container.acquireLease(15, proposedLeaseId); AccessCondition condition = new AccessCondition(); - condition.setLeaseID(leaseId); + condition.setLeaseID(proposedLeaseId); OperationContext operationContext1 = new OperationContext(); this.container.renewLease(condition, null/* BlobRequestOptions */, operationContext1); assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); this.container.releaseLease(condition); // infinite - proposedLeaseId = UUID.randomUUID().toString(); - leaseId = this.container.acquireLease(null /* infinite lease */, proposedLeaseId); + proposedLeaseId = this.container.acquireLease(); condition = new AccessCondition(); - condition.setLeaseID(leaseId); + condition.setLeaseID(proposedLeaseId); OperationContext operationContext2 = new OperationContext(); this.container.renewLease(condition, null/* BlobRequestOptions */, operationContext2); assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); @@ -345,7 +358,7 @@ public void testBlobLeaseBreak() throws StorageException, IOException, URISyntax final CloudBlob blobRef = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 128, null); // Get Lease - String leaseID = blobRef.acquireLease(null, null); + String leaseID = blobRef.acquireLease(); OperationContext operationContext = new OperationContext(); final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/SasTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/SasTests.java index 5111e6ebed047..adcce4ede6add 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/SasTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/SasTests.java @@ -30,18 +30,14 @@ import java.util.NoSuchElementException; import java.util.TimeZone; -import junit.framework.Assert; - import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResponseReceivedEvent; -import com.microsoft.azure.storage.RetryNoRetry; import com.microsoft.azure.storage.SecondaryTests; import com.microsoft.azure.storage.SendingRequestEvent; import com.microsoft.azure.storage.StorageCredentials; @@ -61,7 +57,7 @@ public class SasTests { protected CloudBlockBlob blob; @Before - public void leaseTestMethodSetup() throws URISyntaxException, StorageException, IOException { + public void blobSasTestMethodSetup() throws URISyntaxException, StorageException, IOException { this.container = BlobTestHelper.getRandomContainerReference(); this.container.create(); @@ -70,7 +66,7 @@ public void leaseTestMethodSetup() throws URISyntaxException, StorageException, } @After - public void leaseTestMethodTearDown() throws StorageException { + public void blobSasTestMethodTearDown() throws StorageException { this.container.deleteIfExists(); } @@ -130,35 +126,16 @@ public void testContainerSaS() throws IllegalArgumentException, StorageException readListContainer.getStorageUri(), this.container.generateSharedAccessSignature(null, "readlist"))); assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), containerFromUri.getServiceClient() .getCredentials().getClass().toString()); - - // pass in a client which will have different permissions and check the sas permissions are used - // and that the properties set in the old service client are passed to the new client - CloudBlobClient bClient = this.container.getServiceClient(); - - // set some arbitrary settings to make sure they are passed on - bClient.getDefaultRequestOptions().setConcurrentRequestCount(5); - bClient.setDirectoryDelimiter("%"); - bClient.getDefaultRequestOptions().setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY); - bClient.getDefaultRequestOptions().setSingleBlobPutThresholdInBytes(5 * Constants.MB); - bClient.getDefaultRequestOptions().setTimeoutIntervalInMs(1000); - bClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry()); - - containerFromUri = new CloudBlobContainer(PathUtility.addToQuery(readListContainer.getStorageUri(), - this.container.generateSharedAccessSignature(null, "readlist")), this.container.getServiceClient()); - assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), containerFromUri.getServiceClient() + + // create credentials from sas + StorageCredentials creds = new StorageCredentialsSharedAccessSignature( + this.container.generateSharedAccessSignature(null, "readlist")); + CloudBlobClient bClient = new CloudBlobClient(this.container.getServiceClient().getStorageUri(), creds); + + CloudBlobContainer containerFromClient = bClient.getContainerReference(this.container.getName()); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), containerFromClient.getServiceClient() .getCredentials().getClass().toString()); - - assertEquals(bClient.getDefaultRequestOptions().getConcurrentRequestCount(), containerFromUri - .getServiceClient().getDefaultRequestOptions().getConcurrentRequestCount()); - assertEquals(bClient.getDirectoryDelimiter(), containerFromUri.getServiceClient().getDirectoryDelimiter()); - assertEquals(bClient.getDefaultRequestOptions().getLocationMode(), containerFromUri.getServiceClient() - .getDefaultRequestOptions().getLocationMode()); - assertEquals(bClient.getDefaultRequestOptions().getSingleBlobPutThresholdInBytes(), containerFromUri - .getServiceClient().getDefaultRequestOptions().getSingleBlobPutThresholdInBytes()); - assertEquals(bClient.getDefaultRequestOptions().getTimeoutIntervalInMs(), containerFromUri.getServiceClient() - .getDefaultRequestOptions().getTimeoutIntervalInMs()); - assertEquals(bClient.getDefaultRequestOptions().getRetryPolicyFactory().getClass(), containerFromUri - .getServiceClient().getDefaultRequestOptions().getRetryPolicyFactory().getClass()); + assertEquals(bClient, containerFromClient.getServiceClient()); } @Test @@ -196,10 +173,10 @@ public void testContainerUpdateSAS() throws InvalidKeyException, StorageExceptio try { BlobTestHelper.uploadNewBlob(sasContainer, BlobType.BLOCK_BLOB, "blockblob", 64, null); - Assert.fail(); + fail(); } catch (StorageException ex) { - Assert.assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); } } @@ -312,38 +289,20 @@ public void testBlobSaS() throws InvalidKeyException, IllegalArgumentException, // do not give the client and check that the new blob's client has the correct perms CloudBlob blobFromUri = new CloudBlockBlob(PathUtility.addToQuery(this.blob.getStorageUri(), - this.blob.generateSharedAccessSignature(null, "readperm")), null); + this.blob.generateSharedAccessSignature(null, "readperm"))); assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), blobFromUri.getServiceClient() .getCredentials().getClass().toString()); - // pass in a client which will have different permissions and check the sas permissions are used - // and that the properties set in the old service client are passed to the new client - CloudBlobClient bClient = sasBlob.getServiceClient(); - - // set some arbitrary settings to make sure they are passed on - bClient.getDefaultRequestOptions().setConcurrentRequestCount(5); - bClient.setDirectoryDelimiter("%"); - bClient.getDefaultRequestOptions().setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY); - bClient.getDefaultRequestOptions().setSingleBlobPutThresholdInBytes(5 * Constants.MB); - bClient.getDefaultRequestOptions().setTimeoutIntervalInMs(1000); - bClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry()); + // create credentials from sas + StorageCredentials creds = new StorageCredentialsSharedAccessSignature( + this.blob.generateSharedAccessSignature(null, "readperm")); + CloudBlobClient bClient = new CloudBlobClient(sasBlob.getServiceClient().getStorageUri(), creds); - blobFromUri = new CloudBlockBlob(PathUtility.addToQuery(this.blob.getStorageUri(), - this.blob.generateSharedAccessSignature(null, "readperm")), bClient); - assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), blobFromUri.getServiceClient() + CloudBlockBlob blobFromClient = bClient.getContainerReference(this.blob.getContainer().getName()) + .getBlockBlobReference(this.blob.getName()); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), blobFromClient.getServiceClient() .getCredentials().getClass().toString()); - - assertEquals(bClient.getDefaultRequestOptions().getConcurrentRequestCount(), blobFromUri.getServiceClient() - .getDefaultRequestOptions().getConcurrentRequestCount()); - assertEquals(bClient.getDirectoryDelimiter(), blobFromUri.getServiceClient().getDirectoryDelimiter()); - assertEquals(bClient.getDefaultRequestOptions().getLocationMode(), blobFromUri.getServiceClient() - .getDefaultRequestOptions().getLocationMode()); - assertEquals(bClient.getDefaultRequestOptions().getSingleBlobPutThresholdInBytes(), blobFromUri - .getServiceClient().getDefaultRequestOptions().getSingleBlobPutThresholdInBytes()); - assertEquals(bClient.getDefaultRequestOptions().getTimeoutIntervalInMs(), blobFromUri.getServiceClient() - .getDefaultRequestOptions().getTimeoutIntervalInMs()); - assertEquals(bClient.getDefaultRequestOptions().getRetryPolicyFactory().getClass(), blobFromUri - .getServiceClient().getDefaultRequestOptions().getRetryPolicyFactory().getClass()); + assertEquals(bClient, blobFromClient.getServiceClient()); } @Test @@ -431,10 +390,10 @@ private static void testAccess(String sasToken, EnumSet list1 = topDir1.listFilesAndDirectories(); assertTrue(list1.iterator().hasNext()); @@ -259,9 +267,9 @@ public void CloudFileDirectoryListFilesAndDirectories() throws StorageException, */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryListFilesAndDirectoriesMaxResultsValidation() + public void testCloudFileDirectoryListFilesAndDirectoriesMaxResultsValidation() throws StorageException, URISyntaxException { - if (CloudFileDirectorySetup(this.share)) { + if (doCloudFileDirectorySetup(this.share)) { CloudFileDirectory topDir = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); @@ -290,8 +298,8 @@ public void CloudFileDirectoryListFilesAndDirectoriesMaxResultsValidation() */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryWithFilesDelete() throws URISyntaxException, StorageException { - if (CloudFileDirectorySetup(this.share)) { + public void testCloudFileDirectoryWithFilesDelete() throws URISyntaxException, StorageException { + if (doCloudFileDirectorySetup(this.share)) { CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference( "TopDir1/MidDir1/EndDir1"); CloudFile file1 = dir1.getFileReference("EndFile1"); @@ -311,6 +319,95 @@ public void CloudFileDirectoryWithFilesDelete() throws URISyntaxException, Stora assertFalse(dir1.exists()); } } + + /** + * Check uploading/downloading directory metadata. + * + * @throws StorageException + * @throws URISyntaxException + */ + @Test + public void testCloudFileDirectoryUploadMetadata() throws StorageException, URISyntaxException { + CloudFileDirectory directory = this.share.getRootDirectoryReference(); + directory.downloadAttributes(); + Assert.assertEquals(0, directory.getMetadata().size()); + + directory.getMetadata().put("key1", "value1"); + directory.uploadMetadata(); + directory.getMetadata().clear(); + + directory.downloadAttributes(); + Assert.assertEquals(1, directory.getMetadata().size()); + Assert.assertEquals("value1", directory.getMetadata().get("key1")); + + directory.getMetadata().clear(); + directory.uploadMetadata(); + directory.getMetadata().put("key2", "value2"); + + directory.downloadAttributes(); + Assert.assertEquals(0, directory.getMetadata().size()); + } + + /** + * Check if a directory reference with metadata will still have that metadata after being created. + * + * @throws StorageException + * @throws URISyntaxException + */ + @Test + public void testCreateDirectoryWithMetadata() throws StorageException, URISyntaxException { + String directoryName = "newDirectory1"; + CloudFileDirectory directory = new CloudFileDirectory( + PathUtility.appendPathToUri(this.share.getStorageUri(), directoryName), directoryName, this.share); + Assert.assertEquals(0, directory.getMetadata().size()); + + directory.getMetadata().put("key1", "value1"); + directory.createIfNotExists(); + directory.getMetadata().clear(); + + directory.downloadAttributes(); + Assert.assertEquals(1, directory.getMetadata().size()); + Assert.assertEquals("value1", directory.getMetadata().get("key1")); + } + + /** + * Check uploading/downloading invalid directory metadata. + * @throws URISyntaxException + * @throws StorageException + */ + @Test + public void testCloudFileDirectoryInvalidMetadata() throws StorageException, URISyntaxException { + CloudFileDirectory directory = this.share.getRootDirectoryReference(); + + // test client-side fails correctly + testMetadataFailures(directory, null, "value1", true); + testMetadataFailures(directory, "", "value1", true); + testMetadataFailures(directory, " ", "value1", true); + testMetadataFailures(directory, "\n \t", "value1", true); + + testMetadataFailures(directory, "key1", null, false); + testMetadataFailures(directory, "key1", "", false); + testMetadataFailures(directory, "key1", " ", false); + testMetadataFailures(directory, "key1", "\n \t", false); + } + + private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) { + directory.getMetadata().put(key, value); + try { + directory.uploadMetadata(); + fail(SR.METADATA_KEY_INVALID); + } + catch (StorageException e) { + if (badKey) { + assertEquals(SR.METADATA_KEY_INVALID, e.getMessage()); + } + else { + assertEquals(SR.METADATA_VALUE_INVALID, e.getMessage()); + } + } + + directory.getMetadata().remove(key); + } /* [TestMethod] @@ -397,7 +494,7 @@ public void CloudFileDirectoryConditionalAccess() */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxException, StorageException { CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); CloudFile file = dir.getFileReference("file1"); @@ -427,7 +524,7 @@ public void CloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxExcep */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryCreateDirectoryWithoutParent() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryCreateDirectoryWithoutParent() throws URISyntaxException, StorageException { CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); CloudFileDirectory dir2 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1/Dir2"); try { @@ -452,7 +549,7 @@ public void CloudFileDirectoryCreateDirectoryWithoutParent() throws URISyntaxExc */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryGetParent() throws StorageException, URISyntaxException { + public void testCloudFileDirectoryGetParent() throws StorageException, URISyntaxException { CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("Dir1") .getFileReference("File1"); assertEquals("File1", file.getName()); @@ -487,7 +584,8 @@ public void CloudFileDirectoryGetParent() throws StorageException, URISyntaxExce */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() + throws URISyntaxException, StorageException { CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); CloudFileDirectory subDirectory = directory.getDirectoryReference("MidDir1"); CloudFileDirectory parent = subDirectory.getParent(); @@ -503,7 +601,7 @@ public void CloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() throws UR */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryGetParentOnRoot() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryGetParentOnRoot() throws URISyntaxException, StorageException { CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); CloudFileDirectory parent = root.getParent(); assertNotNull(parent); @@ -520,8 +618,8 @@ public void CloudFileDirectoryGetParentOnRoot() throws URISyntaxException, Stora */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryHierarchicalTraversal() throws URISyntaxException, StorageException { - ////Traverse hierarchically starting with length 1 + public void testCloudFileDirectoryHierarchicalTraversal() throws URISyntaxException, StorageException { + // Traverse hierarchically starting with length 1 CloudFileDirectory directory1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); CloudFileDirectory subdir1 = directory1.getDirectoryReference("Dir2"); CloudFileDirectory parent1 = subdir1.getParent(); @@ -548,7 +646,7 @@ public void CloudFileDirectoryHierarchicalTraversal() throws URISyntaxException, */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryFileParentValidate() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryFileParentValidate() throws URISyntaxException, StorageException { CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1") .getDirectoryReference("MidDir1").getDirectoryReference("EndDir1").getFileReference("EndFile1"); CloudFileDirectory directory = file.getParent(); @@ -564,7 +662,7 @@ public void CloudFileDirectoryFileParentValidate() throws URISyntaxException, St */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryGetEmptySubDirectory() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryGetEmptySubDirectory() throws URISyntaxException, StorageException { CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); try { @@ -584,7 +682,7 @@ public void CloudFileDirectoryGetEmptySubDirectory() throws URISyntaxException, */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - public void CloudFileDirectoryAbsoluteUriAppended() throws URISyntaxException, StorageException { + public void testCloudFileDirectoryAbsoluteUriAppended() throws URISyntaxException, StorageException { CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference( this.share.getUri().toString()); assertEquals(PathUtility.appendPathToUri(this.share.getStorageUri(), this.share.getUri().toString()) @@ -605,7 +703,8 @@ public void CloudFileDirectoryAbsoluteUriAppended() throws URISyntaxException, S */ @Test @Category({ DevFabricTests.class, DevStoreTests.class }) - public void CloudFileDirectoryDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException { + public void testCloudFileDirectoryDeleteIfExistsErrorCode() + throws URISyntaxException, StorageException, IOException { final CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference( "directory"); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java index b56fa705a517a..0cb7a6079ef81 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java @@ -16,8 +16,15 @@ import static org.junit.Assert.*; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.URISyntaxException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.TimeZone; import org.junit.After; import org.junit.Assert; @@ -34,6 +41,7 @@ import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.TestRunners.SlowTests; import com.microsoft.azure.storage.core.SR; /** @@ -171,6 +179,98 @@ public void testCloudFileShareExists() throws StorageException { assertFalse(this.share.exists()); } + /** + * Set and delete share permissions + * + * @throws URISyntaxException + * @throws StorageException + * @throws InterruptedException + */ + @Test + @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class }) + public void testCloudFileShareSetPermissions() + throws StorageException, InterruptedException, URISyntaxException { + CloudFileClient client = FileTestHelper.createCloudFileClient(); + this.share.create(); + + FileSharePermissions permissions = this.share.downloadPermissions(); + assertEquals(0, permissions.getSharedAccessPolicies().size()); + + final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + final Date start = cal.getTime(); + cal.add(Calendar.MINUTE, 30); + final Date expiry = cal.getTime(); + + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.LIST)); + policy.setSharedAccessStartTime(start); + policy.setSharedAccessExpiryTime(expiry); + permissions.getSharedAccessPolicies().put("key1", policy); + + // Set permissions and wait for them to propagate + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + // Check if permissions were set + CloudFileShare share2 = client.getShareReference(this.share.getName()); + assertPermissionsEqual(permissions, share2.downloadPermissions()); + + // Clear permissions and wait for them to propagate + permissions.getSharedAccessPolicies().clear(); + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + // Check if permissions were cleared + assertPermissionsEqual(permissions, share2.downloadPermissions()); + } + + /** + * Get permissions from string + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCloudFileSharePermissionsFromString() { + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + + policy.setPermissionsFromString("rwdl"); + assertEquals(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.DELETE, SharedAccessFilePermissions.LIST), policy.getPermissions()); + + policy.setPermissionsFromString("rwl"); + assertEquals(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.LIST), policy.getPermissions()); + + policy.setPermissionsFromString("wr"); + assertEquals(EnumSet.of(SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.READ), + policy.getPermissions()); + + policy.setPermissionsFromString("d"); + assertEquals(EnumSet.of(SharedAccessFilePermissions.DELETE), policy.getPermissions()); + } + + /** + * Write permission to string + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCloudFileSharePermissionsToString() { + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.DELETE, SharedAccessFilePermissions.LIST)); + assertEquals("rwdl", policy.permissionsToString()); + + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.LIST)); + assertEquals("rwl", policy.permissionsToString()); + + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.READ)); + assertEquals("rw", policy.permissionsToString()); + + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.DELETE)); + assertEquals("d", policy.permissionsToString()); + } + /** * Check uploading/downloading share metadata. * @@ -242,6 +342,91 @@ private static void testMetadataFailures(CloudFileShare share, String key, Strin share.getMetadata().remove(key); } + /** + * Tests whether Share Stats can be updated and downloaded. + * + * @throws StorageException + * @throws IOException + * @throws URISyntaxException + */ + @Test + @Category({ CloudTests.class }) + public void testGetShareStats() throws StorageException, IOException, URISyntaxException { + share.createIfNotExists(); + ShareStats stats = share.getStats(); + Assert.assertNotNull(stats); + Assert.assertEquals(0, stats.getUsage()); + + FileTestHelper.uploadNewFile(share, 512, null); + + stats = share.getStats(); + Assert.assertNotNull(stats); + Assert.assertEquals(1, stats.getUsage()); + } + + /** + * Test that Share Quota can be set, but only to allowable values. + * + * @throws StorageException + * @throws URISyntaxException + */ + @ Test + public void testCloudFileShareQuota() throws StorageException, URISyntaxException { + // Share quota defaults to 5120 + this.share.createIfNotExists(); + this.share.downloadAttributes(); + assertNotNull(this.share.getProperties().getShareQuota()); + int shareQuota = FileConstants.MAX_SHARE_QUOTA; + assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue()); + + // Upload new share quota + shareQuota = 8; + this.share.getProperties().setShareQuota(shareQuota); + this.share.uploadProperties(); + this.share.downloadAttributes(); + assertNotNull(this.share.getProperties().getShareQuota()); + assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue()); + this.share.delete(); + + // Create a share with quota already set + shareQuota = 16; + this.share = FileTestHelper.getRandomShareReference(); + this.share.getProperties().setShareQuota(shareQuota); + this.share.create(); + this.share.downloadAttributes(); + assertNotNull(this.share.getProperties().getShareQuota()); + assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue()); + + // Attempt to set illegal share quota + try { + shareQuota = FileConstants.MAX_SHARE_QUOTA + 1; + this.share.getProperties().setShareQuota(shareQuota); + fail(); + } catch (IllegalArgumentException e) { + assertEquals(String.format(SR.PARAMETER_NOT_IN_RANGE, "Share Quota", 1, FileConstants.MAX_SHARE_QUOTA), + e.getMessage()); + } + } + + /** + * Test that Share Quota can be set, but only to allowable values. + * + * @throws StorageException + * @throws URISyntaxException + */ + @ Test + public void testCloudFileShareQuotaListing() throws StorageException, URISyntaxException { + int shareQuota = 16; + this.share.getProperties().setShareQuota(shareQuota); + this.share.createIfNotExists(); + + Iterable shares = this.share.getServiceClient().listShares(this.share.getName()); + + for (CloudFileShare fileShare : shares) { + assertEquals(shareQuota, fileShare.getProperties().getShareQuota().intValue()); + } + } + /** * Test specific deleteIfExists case. * @@ -276,7 +461,24 @@ public void eventOccurred(SendingRequestEvent eventArg) { this.share.create(); - // Container deletes succeed before garbage collection occurs. + // Share deletes succeed before garbage collection occurs. assertTrue(this.share.deleteIfExists(null, null, ctx)); } + + private static void assertPermissionsEqual(FileSharePermissions expected, FileSharePermissions actual) { + HashMap expectedPolicies = expected.getSharedAccessPolicies(); + HashMap actualPolicies = actual.getSharedAccessPolicies(); + assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessFilePolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessFilePolicy actualPolicy = actualPolicies.get(name); + assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime().toString(), + actualPolicy.getSharedAccessStartTime().toString()); + assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime().toString(), + actualPolicy.getSharedAccessExpiryTime().toString()); + } + } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java index 4dc419335fda0..f53cd91a78e36 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java @@ -20,33 +20,51 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.lang.reflect.Constructor; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; +import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.Random; +import java.util.TimeZone; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.NameValidator; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RetryNoRetry; import com.microsoft.azure.storage.SendingRequestEvent; +import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.TestRunners.SlowTests; +import com.microsoft.azure.storage.blob.BlobProperties; +import com.microsoft.azure.storage.blob.BlobTestHelper; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.blob.CloudBlockBlob; +import com.microsoft.azure.storage.blob.CloudPageBlob; +import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions; +import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.Utility; /** * File Tests @@ -70,7 +88,7 @@ public void fileTestMethodTearDown() throws StorageException { * Test file name validation. */ @Test - public void CloudFileNameValidation() + public void testCloudFileNameValidation() { NameValidator.validateFileName("alpha"); NameValidator.validateFileName("4lphanum3r1c"); @@ -115,6 +133,21 @@ public void testCloudFileCreateAndDelete() throws URISyntaxException, StorageExc assertTrue(file.exists()); file.delete(); } + + @Test + public void testCloudFileCreate() throws StorageException, URISyntaxException { + CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1"); + + assertFalse(file.exists()); + + // Create + file.create(512); + assertTrue(file.exists()); + + // Create again (should succeed) + file.create(128); + assertTrue(file.exists()); + } /** * Test file constructor. @@ -125,18 +158,19 @@ public void testCloudFileCreateAndDelete() throws URISyntaxException, StorageExc @Test public void testCloudFileConstructor() throws URISyntaxException, StorageException { CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1"); - CloudFile file2 = new CloudFile(file.getStorageUri(), null); + CloudFile file2 = new CloudFile(file.getStorageUri(), file.getServiceClient().getCredentials()); assertEquals(file.getName(), file2.getName()); assertEquals(file.getStorageUri(), file2.getStorageUri()); assertEquals(file.getShare().getStorageUri(), file2.getShare().getStorageUri()); - assertEquals(file.getServiceClient().getStorageUri(), file2.getServiceClient().getStorageUri()); + assertEquals(FileTestHelper.ensureTrailingSlash(file.getServiceClient().getStorageUri()), + FileTestHelper.ensureTrailingSlash(file2.getServiceClient().getStorageUri())); CloudFile file3 = new CloudFile(file2); assertEquals(file3.getName(), file2.getName()); assertEquals(file3.getStorageUri(), file2.getStorageUri()); assertEquals(file3.getShare().getStorageUri(), file2.getShare().getStorageUri()); - assertEquals(file3.getServiceClient().getStorageUri(), file2.getServiceClient().getStorageUri()); - + assertEquals(FileTestHelper.ensureTrailingSlash(file3.getServiceClient().getStorageUri()), + FileTestHelper.ensureTrailingSlash(file2.getServiceClient().getStorageUri())); } /** @@ -353,6 +387,203 @@ public void testCloudFileCreateWithMetadata() throws URISyntaxException, Storage file.downloadAttributes(); assertEquals(0, file.getMetadata().size()); } + + @Test + @Category(SlowTests.class) + public void testCopyBlockBlobSas() throws Exception { + // Create source on server. + final CloudBlobContainer container = BlobTestHelper.getRandomContainerReference(); + try { + container.create(); + final CloudBlockBlob source = container.getBlockBlobReference("source"); + + source.getMetadata().put("Test", "value"); + final String data = "String data"; + source.uploadText(data, Constants.UTF8_CHARSET, null, null, null); + + final CloudFile destination = doCloudBlobCopy(source, data.length()); + + final String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null); + assertEquals(data, copyData); + } + finally { + container.deleteIfExists(); + } + } + + @Test + @Category(SlowTests.class) + public void testCopyPageBlobSas() throws Exception { + // Create source on server. + final CloudBlobContainer container = BlobTestHelper.getRandomContainerReference(); + try { + container.create(); + final CloudPageBlob source = container.getPageBlobReference("source"); + + source.getMetadata().put("Test", "value"); + final int length = 512; + final ByteArrayInputStream data = BlobTestHelper.getRandomDataStream(length); + source.upload(data, length); + + final CloudFile destination = doCloudBlobCopy(source, length); + + final ByteArrayOutputStream copyData = new ByteArrayOutputStream(); + destination.download(copyData); + BlobTestHelper.assertStreamsAreEqual(data, new ByteArrayInputStream(copyData.toByteArray())); + } + finally { + container.deleteIfExists(); + } + } + + @Test + @Category(SlowTests.class) + public void testCopyFileSasToSas() throws InvalidKeyException, URISyntaxException, StorageException, + IOException, InterruptedException { + this.doCloudFileCopy(true, true); + } + + // There is no testCopyFileToSas() because there is no way for the SAS destination to access a shared key source. + // This means it would require the source to have public access, which files do not support. + + @Test + @Category(SlowTests.class) + public void testCopyFileSas() throws InvalidKeyException, URISyntaxException, StorageException, + IOException, InterruptedException { + this.doCloudFileCopy(true, false); + } + + @Test + @Category(SlowTests.class) + public void testCopyFile() throws InvalidKeyException, URISyntaxException, StorageException, IOException, + InterruptedException { + // This works because we use Shared Key for source and destination. + // The request is then signed with the key so the service knows we are authorized to access both. + this.doCloudFileCopy(false, false); + } + + @Test + public void testCopyWithChineseChars() throws StorageException, IOException, URISyntaxException + { + String data = "sample data chinese chars 阿䶵"; + CloudFileDirectory rootDirectory = this.share.getRootDirectoryReference(); + CloudFile copySource = rootDirectory.getFileReference("sourcechinescharsblob阿䶵.txt"); + copySource.uploadText(data); + + assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob阿䶵.txt", copySource.getUri().toString()); + assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", copySource + .getUri().toASCIIString()); + + CloudFile copyDestination = rootDirectory.getFileReference("destchinesecharsblob阿䶵.txt"); + + assertEquals(rootDirectory.getUri() + "/destchinesecharsblob阿䶵.txt", copyDestination.getUri().toString()); + assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", + copyDestination.getUri().toASCIIString()); + + OperationContext ctx = new OperationContext(); + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); + + try { + CloudFileDirectory rootDirectory = CloudFileTests.this.share.getRootDirectoryReference(); + + // Test the copy destination request url + assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", + con.getURL().toString()); + + // Test the copy source request property + assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", + con.getRequestProperty("x-ms-copy-source")); + } catch (Exception e) { + fail("This code should not generate any exceptions."); + } + } + }); + + copyDestination.startCopy(copySource.getUri(), null, null, null, ctx); + copyDestination.startCopy(copySource, null, null, null, ctx); + } + + @Test + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) + public void testCopyFileWithMetadataOverride() throws URISyntaxException, StorageException, IOException, + InterruptedException { + Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE); + String data = "String data"; + CloudFile source = this.share.getRootDirectoryReference().getFileReference("source"); + FileTestHelper.setFileProperties(source); + + // do this to make sure the set MD5 can be compared, otherwise when the dummy value + // doesn't match the actual MD5 an exception would be thrown + FileRequestOptions options = new FileRequestOptions(); + options.setDisableContentMD5Validation(true); + + source.getMetadata().put("Test", "value"); + source.uploadText(data); + + CloudFile copy = this.share.getRootDirectoryReference().getFileReference("copy"); + copy.getMetadata().put("Test2", "value2"); + String copyId = copy.startCopy(FileTestHelper.defiddler(source)); + FileTestHelper.waitForCopy(copy); + + assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus()); + assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), + copy.getCopyState().getSource().getPath()); + assertEquals(data.length(), copy.getCopyState().getTotalBytes().intValue()); + assertEquals(data.length(), copy.getCopyState().getBytesCopied().intValue()); + assertEquals(copyId, copy.getCopyState().getCopyId()); + assertTrue(copy.getCopyState().getCompletionTime().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0); + + String copyData = copy.downloadText(Constants.UTF8_CHARSET, null, options, null); + assertEquals(data, copyData); + + copy.downloadAttributes(); + source.downloadAttributes(); + FileProperties prop1 = copy.getProperties(); + FileProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value2", copy.getMetadata().get("Test2")); + assertFalse(copy.getMetadata().containsKey("Test")); + + copy.delete(); + } + + + /** + * Start copying a file and then abort + * + * @throws StorageException + * @throws URISyntaxException + * @throws IOException + * @throws InterruptedException + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCopyFileAbort() throws StorageException, URISyntaxException, IOException { + final int length = 128; + CloudFile originalFile = FileTestHelper.uploadNewFile(this.share, length, null); + CloudFile copyFile = this.share.getRootDirectoryReference().getFileReference(originalFile.getName() + "copyed"); + copyFile.startCopy(originalFile); + + try { + copyFile.abortCopy(copyFile.getProperties().getCopyState().getCopyId()); + fail(); + } + catch (StorageException e) { + if (!e.getErrorCode().contains("NoPendingCopyOperation")) { + throw e; + } + } + } /** * Test file stream uploading. @@ -1082,7 +1313,7 @@ public void testCloudFileOpenOutputStreamNoArgs() throws URISyntaxException, Sto */ @Test @Category({ DevFabricTests.class, DevStoreTests.class }) - public void CloudFileDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException { + public void testCloudFileDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException { CloudFileClient client = FileTestHelper.createCloudFileClient(); CloudFileShare share = client.getShareReference(FileTestHelper.generateRandomShareName()); share.create(); @@ -1129,4 +1360,178 @@ public void eventOccurred(SendingRequestEvent eventArg) { file.create(2); assertFalse(file.deleteIfExists(null, null, ctx)); } + + /** + * @throws StorageException + * @throws URISyntaxException + * @throws IOException + * @throws InterruptedException + */ + @Test + @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) + public void testFileNamePlusEncoding() throws StorageException, URISyntaxException, IOException, + InterruptedException { + CloudFile originalFile = FileTestHelper.uploadNewFile(this.share, 1024 /* length */, null); + CloudFile copyFile = this.share.getRootDirectoryReference().getFileReference(originalFile.getName() + "copyed"); + + copyFile.startCopy(originalFile); + FileTestHelper.waitForCopy(copyFile); + + copyFile.downloadAttributes(); + originalFile.downloadAttributes(); + FileProperties prop1 = copyFile.getProperties(); + FileProperties prop2 = originalFile.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + } + + private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception { + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + cal.setTime(new Date()); + cal.add(Calendar.MINUTE, 5); + + // Source SAS must have read permissions + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); + policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ)); + policy.setSharedAccessExpiryTime(cal.getTime()); + + String sasToken = source.generateSharedAccessSignature(policy, null, null); + + // Get destination reference + final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination"); + + // Start copy and wait for completion + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + Constructor blobType = source.getClass().getConstructor(URI.class); + String copyId = destination.startCopy(blobType.newInstance(credentials.transformUri(source.getUri()))); + FileTestHelper.waitForCopy(destination); + destination.downloadAttributes(); + + // Check original file references for equality + assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); + assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), + destination.getCopyState().getSource().getPath()); + assertEquals(length, destination.getCopyState().getTotalBytes().intValue()); + assertEquals(length, destination.getCopyState().getBytesCopied().intValue()); + assertEquals(copyId, destination.getProperties().getCopyState().getCopyId()); + + // Attempt to abort the completed copy operation. + try { + destination.abortCopy(destination.getCopyState().getCopyId()); + FileTestHelper.waitForCopy(destination); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode()); + } + + assertNotNull(destination.getProperties().getEtag()); + assertFalse(source.getProperties().getEtag().equals(destination.getProperties().getEtag())); + + source.downloadAttributes(); + FileProperties prop1 = destination.getProperties(); + BlobProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value", destination.getMetadata().get("Test")); + return destination; + } + + private void doCloudFileCopy(boolean sourceIsSas, boolean destinationIsSas) + throws URISyntaxException, StorageException, IOException, InvalidKeyException, InterruptedException { + // Create source on server. + final CloudFile source = this.share.getRootDirectoryReference().getFileReference("source"); + + final String data = "String data"; + source.getMetadata().put("Test", "value"); + source.uploadText(data, Constants.UTF8_CHARSET, null, null, null); + + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + cal.setTime(new Date()); + cal.add(Calendar.MINUTE, 5); + + CloudFile copySource = source; + if (sourceIsSas) { + // Source SAS must have read permissions + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ)); + policy.setSharedAccessExpiryTime(cal.getTime()); + + String sasToken = source.generateSharedAccessSignature(policy, null, null); + + // Get source file reference + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + copySource = new CloudFile(credentials.transformUri(source.getUri())); + } + + // Get destination reference + final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination"); + + CloudFile copyDestination = destination; + if (destinationIsSas) { + // Destination SAS must have write permissions + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE)); + policy.setSharedAccessExpiryTime(cal.getTime()); + + String sasToken = destination.generateSharedAccessSignature(policy, null, null); + + // Get destination file reference + StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); + copyDestination = new CloudFile(destination.getUri(), + destination.getServiceClient().getCredentials()); + } + + // Start copy and wait for completion + String copyId = copyDestination.startCopy(copySource); + FileTestHelper.waitForCopy(copyDestination); + destination.downloadAttributes(); + + // Check original file references for equality + assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); + assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), + destination.getCopyState().getSource().getPath()); + assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue()); + assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue()); + assertEquals(copyId, destination.getProperties().getCopyState().getCopyId()); + + if (!destinationIsSas) { + try { + copyDestination.abortCopy(destination.getCopyState().getCopyId()); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode()); + } + } + + assertNotNull(destination.getProperties().getEtag()); + assertFalse(source.getProperties().getEtag().equals(destination.getProperties().getEtag())); + + String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null); + assertEquals(data, copyData); + + FileProperties prop1 = destination.getProperties(); + FileProperties prop2 = source.getProperties(); + + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + + assertEquals("value", destination.getMetadata().get("Test")); + + destination.delete(); + source.delete(); + } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileSasTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileSasTests.java new file mode 100644 index 0000000000000..57408592d140b --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileSasTests.java @@ -0,0 +1,430 @@ +/** + * Copyright 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.azure.storage.file; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.NoSuchElementException; +import java.util.TimeZone; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.ResponseReceivedEvent; +import com.microsoft.azure.storage.SecondaryTests; +import com.microsoft.azure.storage.SendingRequestEvent; +import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.azure.storage.StorageEvent; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.TestHelper; +import com.microsoft.azure.storage.TestRunners.CloudTests; +import com.microsoft.azure.storage.TestRunners.DevFabricTests; +import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.TestRunners.SlowTests; +import com.microsoft.azure.storage.core.PathUtility; + +@Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) +public class FileSasTests { + + protected static CloudFileClient fileClient = null; + protected CloudFileShare share; + protected CloudFile file; + + @Before + public void fileSASTestMethodSetup() throws URISyntaxException, StorageException, IOException { + if (fileClient == null) { + fileClient = TestHelper.createCloudFileClient(); + } + this.share = FileTestHelper.getRandomShareReference(); + this.share.create(); + + this.file = (CloudFile) FileTestHelper.uploadNewFile(this.share, 100, null); + } + + @After + public void fileSASTestMethodTearDown() throws StorageException { + this.share.deleteIfExists(); + } + + @Test + public void testApiVersion() throws InvalidKeyException, StorageException, URISyntaxException { + SharedAccessFilePolicy policy = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300); + String sas = this.file.generateSharedAccessSignature(policy, null); + + // should not be appended before signing + assertEquals(-1, sas.indexOf(Constants.QueryConstants.API_VERSION)); + + OperationContext ctx = new OperationContext(); + ctx.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + // should be appended after signing + HttpURLConnection conn = (HttpURLConnection) eventArg.getConnectionObject(); + assertTrue(conn.getURL().toString().indexOf(Constants.QueryConstants.API_VERSION) != -1); + } + }); + + CloudFile sasFile = new CloudFile(new URI(this.file.getUri().toString() + "?" + sas)); + sasFile.uploadMetadata(null, null, ctx); + } + + @Test + public void testDirectorySas() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, InterruptedException { + CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dirFile"); + CloudFile file = dir.getFileReference("dirFile"); + + dir.create(); + file.create(512); + + SharedAccessFilePolicy policy = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300); + + // Test directory SAS with a file SAS token from an identically named file + String sas = file.generateSharedAccessSignature(policy, null); + CloudFileDirectory sasDir = new CloudFileDirectory(new URI(dir.getUri().toString() + "?" + sas)); + try { + sasDir.downloadAttributes(); + fail("This should result in an authentication error."); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode()); + } + + // Test directory SAS with a share SAS token + sas = this.share.generateSharedAccessSignature(policy, null); + sasDir = new CloudFileDirectory(new URI(dir.getUri().toString() + "?" + sas)); + sasDir.downloadAttributes(); + } + + @Test + @Category({ SecondaryTests.class, SlowTests.class }) + public void testShareSAS()throws IllegalArgumentException, StorageException, URISyntaxException, + InvalidKeyException, InterruptedException { + SharedAccessFilePolicy policy1 = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE, + SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300); + SharedAccessFilePolicy policy2 = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300); + FileSharePermissions permissions = new FileSharePermissions(); + + permissions.getSharedAccessPolicies().put("full", policy1); + permissions.getSharedAccessPolicies().put("readlist", policy2); + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + String shareReadListSas = this.share.generateSharedAccessSignature(policy2, null); + CloudFileShare readListShare = + new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), shareReadListSas)); + + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), + readListShare.getServiceClient().getCredentials().getClass().toString()); + + CloudFile fileFromSasShare = readListShare.getRootDirectoryReference().getFileReference(this.file.getName()); + fileFromSasShare.download(new ByteArrayOutputStream()); + + // do not give the client and check that the new share's client has the correct perms + CloudFileShare shareFromUri = new CloudFileShare(PathUtility.addToQuery( + readListShare.getStorageUri(), this.share.generateSharedAccessSignature(null, "readlist"))); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), + shareFromUri.getServiceClient().getCredentials().getClass().toString()); + + // create credentials from sas + StorageCredentials creds = new StorageCredentialsSharedAccessSignature( + this.share.generateSharedAccessSignature(null, "readlist")); + CloudFileClient client = new CloudFileClient(this.share.getServiceClient().getStorageUri(), creds); + + CloudFileShare shareFromClient = client.getShareReference(readListShare.getName()); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), + shareFromClient.getServiceClient().getCredentials().getClass().toString()); + assertEquals(client, shareFromClient.getServiceClient()); + } + + @Test + @Category(SlowTests.class) + public void testShareUpdateSAS() + throws InvalidKeyException, StorageException, IOException, URISyntaxException, InterruptedException { + // Create a policy with read/write access and get SAS. + SharedAccessFilePolicy policy = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE), 300); + FileSharePermissions permissions = new FileSharePermissions(); + + permissions.getSharedAccessPolicies().put("readwrite", policy); + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + String sasToken = this.share.generateSharedAccessSignature(policy, null); + + CloudFile file = FileTestHelper.uploadNewFile(this.share, 64, null); + testAccess(sasToken, EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE), + this.share, file); + + //Change the policy to only read and update SAS. + SharedAccessFilePolicy policy2 = createSharedAccessPolicy(EnumSet.of(SharedAccessFilePermissions.READ), 300); + permissions = new FileSharePermissions(); + + permissions.getSharedAccessPolicies().put("read", policy2); + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + // Extra check to make sure that we have actually updated the SAS token. + String sasToken2 = this.share.generateSharedAccessSignature(policy2, null); + CloudFileShare sasShare = new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), sasToken2)); + + try { + FileTestHelper.uploadNewFile(sasShare, 64, null); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + } + } + + @Test + @Category(SlowTests.class) + public void testShareSASCombinations() + throws StorageException, URISyntaxException, IOException, InvalidKeyException, InterruptedException { + for (int bits = 1; bits < 16; bits++) { + final EnumSet permissionSet = EnumSet.noneOf(SharedAccessFilePermissions.class); + + if ((bits & 0x1) == 0x1) { + permissionSet.add(SharedAccessFilePermissions.READ); + } + + if ((bits & 0x2) == 0x2) { + permissionSet.add(SharedAccessFilePermissions.WRITE); + } + + if ((bits & 0x4) == 0x4) { + permissionSet.add(SharedAccessFilePermissions.DELETE); + } + + if ((bits & 0x8) == 0x8) { + permissionSet.add(SharedAccessFilePermissions.LIST); + } + + SharedAccessFilePolicy policy = createSharedAccessPolicy(permissionSet, 300); + + FileSharePermissions permissions = new FileSharePermissions(); + + permissions.getSharedAccessPolicies().put("readwrite" + bits, policy); + this.share.uploadPermissions(permissions); + Thread.sleep(30000); + + String sasToken = this.share.generateSharedAccessSignature(policy, null); + + CloudFile testFile = FileTestHelper.uploadNewFile(this.share, 64, null); + testAccess(sasToken, permissionSet, this.share, testFile); + } + } + + @Test + public void testFileSASCombinations() throws URISyntaxException, StorageException, InvalidKeyException, IOException { + for (int bits = 1; bits < 8; bits++) { + EnumSet permissionSet = EnumSet.noneOf(SharedAccessFilePermissions.class); + + if ((bits & 0x1) == 0x1) { + permissionSet.add(SharedAccessFilePermissions.READ); + } + + if ((bits & 0x2) == 0x2) { + permissionSet.add(SharedAccessFilePermissions.WRITE); + } + + if ((bits & 0x4) == 0x4) { + permissionSet.add(SharedAccessFilePermissions.DELETE); + } + + CloudFile testFile = FileTestHelper.uploadNewFile(this.share, 512, null); + SharedAccessFilePolicy policy = createSharedAccessPolicy(permissionSet, 300); + String sasToken = testFile.generateSharedAccessSignature(policy, null, null); + + testAccess(sasToken, permissionSet, null, testFile); + } + } + + @Test + public void testFileSAS() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, InterruptedException { + SharedAccessFilePolicy policy = createSharedAccessPolicy( + EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300); + FileSharePermissions perms = new FileSharePermissions(); + + perms.getSharedAccessPolicies().put("readperm", policy); + this.share.uploadPermissions(perms); + Thread.sleep(30000); + + CloudFile sasFile = new CloudFile( + new URI(this.file.getUri().toString() + "?" + this.file.generateSharedAccessSignature(null, "readperm"))); + sasFile.download(new ByteArrayOutputStream()); + + // do not give the client and check that the new file's client has the correct permissions + CloudFile fileFromUri = new CloudFile(PathUtility.addToQuery(this.file.getStorageUri(), + this.file.generateSharedAccessSignature(null, "readperm"))); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), + fileFromUri.getServiceClient().getCredentials().getClass().toString()); + + // create credentials from sas + StorageCredentials creds = new StorageCredentialsSharedAccessSignature( + this.file.generateSharedAccessSignature(policy, null, null)); + CloudFileClient client = new CloudFileClient(sasFile.getServiceClient().getStorageUri(), creds); + + CloudFile fileFromClient = client.getShareReference(this.file.getShare().getName()).getRootDirectoryReference() + .getFileReference(this.file.getName()); + assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), + fileFromClient.getServiceClient().getCredentials().getClass().toString()); + assertEquals(client, fileFromClient.getServiceClient()); + } + + @Test + public void testFileSASWithSharedAccessFileHeaders() throws InvalidKeyException, IllegalArgumentException, + StorageException, URISyntaxException, InterruptedException { + SharedAccessFilePolicy policy = createSharedAccessPolicy(EnumSet.of(SharedAccessFilePermissions.READ, + SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.LIST), 300); + FileSharePermissions perms = new FileSharePermissions(); + + perms.getSharedAccessPolicies().put("rwperm", policy); + this.share.uploadPermissions(perms); + Thread.sleep(30000); + + SharedAccessFileHeaders headers = new SharedAccessFileHeaders(); + headers.setCacheControl("no-cache"); + headers.setContentDisposition("attachment; filename=\"fname.ext\""); + headers.setContentEncoding("gzip"); + headers.setContentLanguage("da"); + headers.setContentType("text/html; charset=utf-8"); + + CloudFile sasFile = new CloudFile( + new URI(this.file.getUri().toString() + "?" + this.file.generateSharedAccessSignature(null, headers, "rwperm"))); + OperationContext context = new OperationContext(); + + context.getSendingRequestEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject(); + assertEquals("no-cache", connection.getHeaderField(Constants.HeaderConstants.CACHE_CONTROL)); + assertEquals("attachment; filename=\"fname.ext\"", + connection.getHeaderField(Constants.HeaderConstants.CONTENT_DISPOSITION)); + assertEquals("gzip", connection.getHeaderField(Constants.HeaderConstants.CONTENT_ENCODING)); + assertEquals("da", connection.getHeaderField(Constants.HeaderConstants.CONTENT_LANGUAGE)); + assertEquals("text/html; charset=utf-8", + connection.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE)); + } + }); + + sasFile.download(new ByteArrayOutputStream(), null, null, context); + } + + private final static SharedAccessFilePolicy createSharedAccessPolicy(EnumSet sap, + int expireTimeInSeconds) { + + Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + calendar.setTime(new Date()); + calendar.add(Calendar.SECOND, expireTimeInSeconds); + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.setPermissions(sap); + policy.setSharedAccessExpiryTime(calendar.getTime()); + return policy; + } + + @SuppressWarnings("unused") + private static void testAccess( + String sasToken, EnumSet permissions, CloudFileShare share, CloudFile file) + throws StorageException, URISyntaxException { + StorageCredentials credentials = new StorageCredentialsSharedAccessSignature(sasToken); + + if (share != null) { + share = new CloudFileShare(credentials.transformUri(share.getUri())); + file = share.getRootDirectoryReference().getFileReference(file.getName()); + } + else { + file = new CloudFile(credentials.transformUri(file.getUri())); + } + + if (share != null) { + if (permissions.contains(SharedAccessFilePermissions.LIST)) { + for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories()); + } + else { + try { + for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories()); + fail(); + } + catch (NoSuchElementException ex) { + assertEquals( + HttpURLConnection.HTTP_NOT_FOUND, ((StorageException) ex.getCause()).getHttpStatusCode()); + } + } + } + + if (permissions.contains(SharedAccessFilePermissions.READ)) { + file.downloadAttributes(); + } + else { + try { + file.downloadAttributes(); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + } + } + + if (permissions.contains(SharedAccessFilePermissions.WRITE)) { + file.uploadMetadata(); + } + else { + try { + file.uploadMetadata(); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + } + } + + if (permissions.contains(SharedAccessFilePermissions.DELETE)) { + file.delete(); + } + else { + try { + file.delete(); + fail(); + } + catch (StorageException ex) { + assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode()); + } + } + } +} \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java index a1ff827961c83..6525b3b8a9881 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java @@ -14,15 +14,18 @@ */ package com.microsoft.azure.storage.file; +import static org.junit.Assert.*; + import java.io.ByteArrayInputStream; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.util.Random; import java.util.UUID; -import junit.framework.Assert; - +import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.TestHelper; /** @@ -39,6 +42,17 @@ protected static String generateRandomFileName() { String shareName = "file" + UUID.randomUUID().toString(); return shareName.replace("-", ""); } + + public static CloudFile uploadNewFile(CloudFileShare share, int length, OperationContext context) + throws StorageException, IOException, URISyntaxException { + String name = generateRandomFileName(); + + CloudFile file = null; + + file = share.getRootDirectoryReference().getFileReference(name); + file.upload(getRandomDataStream(length), length, null, null, context); + return file; + } public static CloudFileShare getRandomShareReference() throws URISyntaxException, StorageException { String shareName = generateRandomShareName(); @@ -47,6 +61,31 @@ public static CloudFileShare getRandomShareReference() throws URISyntaxException return share; } + + static StorageUri ensureTrailingSlash(StorageUri uri) throws URISyntaxException { + URI primary = uri.getPrimaryUri(); + URI secondary = uri.getSecondaryUri(); + + // Add a trailing slash to primary if it did not previously have one + if (primary != null) { + String primaryUri = primary.toString(); + if (!primaryUri.isEmpty() && !primaryUri.substring(primaryUri.length() - 1).equals("/")) { + primaryUri += "/"; + primary = new URI(primaryUri); + } + } + + // Add a trailing slash to secondary if it did not previously have one + if (secondary != null) { + String secondaryUri = secondary.toString(); + if (!secondaryUri.isEmpty() && !secondaryUri.substring(secondaryUri.length() - 1).equals("/")) { + secondaryUri += "/"; + secondary = new URI(secondaryUri); + } + } + + return new StorageUri(primary, secondary); + } protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSize, int bufferOffset) throws StorageException, IOException { @@ -60,12 +99,12 @@ protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSiz file.downloadToByteArray(resultBuffer, bufferOffset, null, options, null); for (int i = 0; i < file.getProperties().getLength(); i++) { - Assert.assertEquals(buffer[i], resultBuffer[bufferOffset + i]); + assertEquals(buffer[i], resultBuffer[bufferOffset + i]); } if (bufferOffset + fileSize < bufferSize) { for (int k = bufferOffset + fileSize; k < bufferSize; k++) { - Assert.assertEquals(0, resultBuffer[k]); + assertEquals(0, resultBuffer[k]); } } } @@ -89,19 +128,19 @@ protected static void doDownloadRangeToByteArrayTest(CloudFile file, int fileSiz downloadSize = length.intValue(); } - Assert.assertEquals(downloadSize, downloadLength); + assertEquals(downloadSize, downloadLength); for (int i = 0; i < bufferOffset; i++) { - Assert.assertEquals(0, resultBuffer[i]); + assertEquals(0, resultBuffer[i]); } for (int j = 0; j < downloadLength; j++) { - Assert.assertEquals(buffer[(int) ((fileOffset != null ? fileOffset : 0) + j)], resultBuffer[bufferOffset + assertEquals(buffer[(int) ((fileOffset != null ? fileOffset : 0) + j)], resultBuffer[bufferOffset + j]); } for (int k = bufferOffset + downloadLength; k < bufferSize; k++) { - Assert.assertEquals(0, resultBuffer[k]); + assertEquals(0, resultBuffer[k]); } } @@ -117,22 +156,22 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) th try { file.downloadRangeToByteArray(1024, (long) 1, resultBuffer, 0); - Assert.fail(); + fail(); } catch (StorageException ex) { - Assert.assertEquals(416, ex.getHttpStatusCode()); + assertEquals(416, ex.getHttpStatusCode()); } try { file.downloadToByteArray(resultBuffer, 1024); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { } try { file.downloadRangeToByteArray(0, (long) 1023, resultBuffer, 2); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { @@ -141,16 +180,16 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) th // negative length try { file.downloadRangeToByteArray(0, (long) -10, resultBuffer, 0); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { } - // negative blob offset + // negative file offset try { file.downloadRangeToByteArray(-10, (long) 20, resultBuffer, 0); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { @@ -159,39 +198,65 @@ protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) th // negative buffer offset try { file.downloadRangeToByteArray(0, (long) 20, resultBuffer, -10); - Assert.fail(); + fail(); } catch (IndexOutOfBoundsException ex) { } } + + public static CloudFile defiddler(CloudFile file) throws URISyntaxException, StorageException { + URI oldUri = file.getUri(); + URI newUri = defiddler(oldUri); + + if (newUri != oldUri) { + CloudFile newFile = new CloudFile(newUri, file.getServiceClient().getCredentials()); + return newFile; + } + else { + return file; + } + } + + public static void waitForCopy(CloudFile file) throws StorageException, InterruptedException { + boolean copyInProgress = true; + while (copyInProgress) { + file.downloadAttributes(); + copyInProgress = (file.getCopyState().getStatus() == CopyStatus.PENDING) + || (file.getCopyState().getStatus() == CopyStatus.ABORTED); + // One second sleep if retry is needed + if (copyInProgress) { + Thread.sleep(1000); + } + } + } public static void assertAreEqual(CloudFile file1, CloudFile file2) { if (file1 == null) { - Assert.assertNull(file2); + assertNull(file2); } else { - Assert.assertNotNull(file2); - Assert.assertEquals(file1.getUri(), file2.getUri()); + assertNotNull(file2); + assertEquals(file1.getUri(), file2.getUri()); assertAreEqual(file1.getProperties(), file2.getProperties()); } } public static void assertAreEqual(FileProperties prop1, FileProperties prop2) { if (prop1 == null) { - Assert.assertNull(prop2); + assertNull(prop2); } else { - Assert.assertNotNull(prop2); - Assert.assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); - Assert.assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); - Assert.assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); - Assert.assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); - Assert.assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); - Assert.assertEquals(prop1.getContentType(), prop2.getContentType()); - Assert.assertEquals(prop1.getEtag(), prop2.getEtag()); - Assert.assertEquals(prop1.getLastModified(), prop2.getLastModified()); - Assert.assertEquals(prop1.getLength(), prop2.getLength()); + assertNotNull(prop2); + assertEquals(prop1.getCacheControl(), prop2.getCacheControl()); + assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition()); + assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding()); + assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage()); + assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); + assertEquals(prop1.getContentType(), prop2.getContentType()); + assertEquals(prop1.getEtag(), prop2.getEtag()); + assertEquals(prop1.getLastModified(), prop2.getLastModified()); + assertEquals(prop1.getLength(), prop2.getLength()); } } @@ -203,4 +268,4 @@ public static void setFileProperties(CloudFile file) { file.getProperties().setContentMD5("MDAwMDAwMDA="); file.getProperties().setContentType("text/html"); } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueClientTests.java index 49737295b8a61..2cfa2411f83b2 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueClientTests.java @@ -121,7 +121,7 @@ public void testListQueuesSegmented() throws URISyntaxException, StorageExceptio metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); for (int i = 0; i < 35; i++) { - CloudQueue q = new CloudQueue(prefix + UUID.randomUUID().toString().toLowerCase(), qClient); + CloudQueue q = qClient.getQueueReference(prefix + UUID.randomUUID().toString().toLowerCase()); q.setMetadata(metadata1); q.create(); } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java index 1883ffef03d1d..60c8195bfb107 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java @@ -35,7 +35,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import com.microsoft.azure.storage.AuthenticationScheme; import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.NameValidator; import com.microsoft.azure.storage.OperationContext; @@ -55,7 +54,6 @@ /** * Queue Tests */ -@SuppressWarnings("deprecation") @Category({ CloudTests.class }) public class CloudQueueTests { @@ -232,16 +230,9 @@ public void testQueueSAS() throws StorageException, URISyntaxException, InvalidK queueClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry()); queueFromUri = new CloudQueue(PathUtility.addToQuery(this.queue.getStorageUri(), - this.queue.generateSharedAccessSignature(null, "readperm")), queueClient); + this.queue.generateSharedAccessSignature(null, "readperm"))); assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), queueFromUri.getServiceClient() .getCredentials().getClass().toString()); - - assertEquals(queueClient.getDefaultRequestOptions().getLocationMode(), queueFromUri.getServiceClient() - .getDefaultRequestOptions().getLocationMode()); - assertEquals(queueClient.getDefaultRequestOptions().getTimeoutIntervalInMs(), queueFromUri.getServiceClient() - .getDefaultRequestOptions().getTimeoutIntervalInMs()); - assertEquals(queueClient.getDefaultRequestOptions().getRetryPolicyFactory().getClass(), queueFromUri - .getServiceClient().getDefaultRequestOptions().getRetryPolicyFactory().getClass()); } static void assertQueuePermissionsEqual(QueuePermissions expected, QueuePermissions actual) { @@ -268,20 +259,16 @@ public void testQueueClientConstructor() throws URISyntaxException, StorageExcep final CloudQueueClient qClient = TestHelper.createCloudQueueClient(); final String queueName = QueueTestHelper.generateRandomQueueName(); - CloudQueue queue1 = new CloudQueue(queueName, qClient); + CloudQueue queue1 = qClient.getQueueReference(queueName); assertEquals(queueName, queue1.getName()); assertTrue(queue1.getUri().toString().endsWith(queueName)); assertEquals(qClient, queue1.getServiceClient()); CloudQueue queue2 = new CloudQueue(new URI(QueueTestHelper.appendQueueName(qClient.getEndpoint(), queueName)), - qClient); + qClient.getCredentials()); assertEquals(queueName, queue2.getName()); - assertEquals(qClient, queue2.getServiceClient()); - - CloudQueue queue3 = new CloudQueue(queueName, qClient); - assertEquals(queueName, queue3.getName()); - assertEquals(qClient, queue3.getServiceClient()); + assertEquals(qClient.getCredentials(), queue2.getServiceClient().getCredentials()); } @Test @@ -305,8 +292,8 @@ public void testGetMetadata() throws StorageException { @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testUploadMetadata() throws URISyntaxException, StorageException { - CloudQueue queueForGet = new CloudQueue(this.queue.getUri(), this.queue.getServiceClient()); - + CloudQueue queueForGet = this.queue.getServiceClient().getQueueReference(this.queue.getName()); + HashMap metadata1 = new HashMap(); metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); this.queue.setMetadata(metadata1); @@ -322,7 +309,7 @@ public void testUploadMetadata() throws URISyntaxException, StorageException { @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testUploadMetadataNullInput() throws URISyntaxException, StorageException { - CloudQueue queueForGet = new CloudQueue(this.queue.getUri(), this.queue.getServiceClient()); + CloudQueue queueForGet = this.queue.getServiceClient().getQueueReference(this.queue.getName()); HashMap metadata1 = new HashMap(); String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); @@ -345,7 +332,7 @@ public void testUploadMetadataNullInput() throws URISyntaxException, StorageExce @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testUploadMetadataClearExisting() throws URISyntaxException, StorageException { - CloudQueue queueForGet = new CloudQueue(this.queue.getUri(), this.queue.getServiceClient()); + CloudQueue queueForGet = this.queue.getServiceClient().getQueueReference(this.queue.getName()); HashMap metadata1 = new HashMap(); String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); @@ -1437,43 +1424,12 @@ public void testSASClientParse() throws StorageException, InvalidKeyException, U CloudQueueClient queueClient1 = new CloudQueueClient(new URI("http://myaccount.queue.core.windows.net/"), new StorageCredentialsSharedAccessSignature(sasString)); - CloudQueue queue1 = new CloudQueue(queueUri, queueClient1); + CloudQueue queue1 = new CloudQueue(queueUri, queueClient1.getCredentials()); queue1.getName(); CloudQueueClient queueClient2 = new CloudQueueClient(new URI("http://myaccount.queue.core.windows.net/"), new StorageCredentialsSharedAccessSignature(sasString)); - CloudQueue queue2 = new CloudQueue(queueUri, queueClient2); + CloudQueue queue2 = new CloudQueue(queueUri, queueClient2.getCredentials()); queue2.getName(); } - - @Test - @Category({ DevFabricTests.class, DevStoreTests.class }) - public void testQueueSharedKeyLite() throws StorageException, URISyntaxException { - CloudQueueClient qClient = TestHelper.createCloudQueueClient(); - qClient.setAuthenticationScheme(AuthenticationScheme.SHAREDKEYLITE); - CloudQueue queue = qClient.getQueueReference(QueueTestHelper.generateRandomQueueName()); - - OperationContext createQueueContext = new OperationContext(); - queue.create(null, createQueueContext); - assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); - - try { - HashMap metadata1 = new HashMap(); - metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); - queue.setMetadata(metadata1); - queue.create(); - fail(); - } - catch (StorageException e) { - assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT); - - } - - queue.downloadAttributes(); - OperationContext createQueueContext2 = new OperationContext(); - queue.create(null, createQueueContext2); - assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); - - queue.delete(); - } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableClientTests.java index 0de86519a5056..706f1577f1a1b 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableClientTests.java @@ -31,7 +31,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import com.microsoft.azure.storage.AuthenticationScheme; import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RequestResult; @@ -602,31 +601,6 @@ public void testTableSASPkRk() throws StorageException, URISyntaxException, Inva } } - @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) - @Test - public void tableCreateAndAttemptCreateOnceExistsSharedKeyLite() throws StorageException, URISyntaxException { - final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); - tClient.setAuthenticationScheme(AuthenticationScheme.SHAREDKEYLITE); - CloudTable table = tClient.getTableReference(TableTestHelper.generateRandomTableName()); - try { - table.create(); - assertTrue(table.exists()); - - // Should fail as it already exists - try { - table.create(); - fail(); - } - catch (StorageException ex) { - assertEquals(ex.getErrorCode(), "TableAlreadyExists"); - } - } - finally { - // cleanup - table.deleteIfExists(); - } - } - @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class }) @Test public void testBackoffTimeOverflow() { diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableODataTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableODataTests.java index a2c213000e77e..402d8c5019acd 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableODataTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableODataTests.java @@ -45,7 +45,7 @@ public void tableODataTestsBeforeMethod() throws StorageException, URISyntaxExce this.table.createIfNotExists(); final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); - this.options = TableRequestOptions.applyDefaults(this.options, tClient); + this.options = TableRequestOptions.populateAndApplyDefaults(this.options, tClient); this.options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); // Insert Entity diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTests.java index 405d89a2298e7..a1b2e1db10b36 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTests.java @@ -137,7 +137,7 @@ private static void testIsUsePathStyleUri(StorageCredentials creds, String table new StorageCredentialsSharedAccessSignature(sasToken)); assertEquals(usePathStyleUris, tableClient.isUsePathStyleUris()); - table = new CloudTable(table.getUri(), tableClient); + table = new CloudTable(table.getUri(), tableClient.getCredentials()); assertEquals(tableEndpoint + "/mytable", table.getUri().toString()); } @@ -515,10 +515,12 @@ private void testTableSas(CloudTableClient tClient) throws InvalidKeyException, "javatables_batch_9", "9"))); assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), tableFromUri.getServiceClient() .getCredentials().getClass().toString()); - - // pass in a client which will have different permissions and check the sas permissions are used - // and that the properties set in the old service client are passed to the new client - CloudTableClient tableClient = policySasTable.getServiceClient(); + + // create credentials from sas + StorageCredentials creds = new StorageCredentialsSharedAccessSignature( + table.generateSharedAccessSignature((SharedAccessTablePolicy) null, identifier, "javatables_batch_0", "0", + "javatables_batch_9", "9")); + CloudTableClient tableClient = new CloudTableClient(policySasTable.getServiceClient().getStorageUri(), creds); // set some arbitrary settings to make sure they are passed on tableClient.getDefaultRequestOptions().setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY); @@ -526,9 +528,7 @@ private void testTableSas(CloudTableClient tClient) throws InvalidKeyException, tableClient.getDefaultRequestOptions().setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); tableClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry()); - tableFromUri = new CloudTable(PathUtility.addToQuery(table.getStorageUri(), table - .generateSharedAccessSignature((SharedAccessTablePolicy) null, identifier, "javatables_batch_0", "0", - "javatables_batch_9", "9")), tableClient); + tableFromUri = tableClient.getTableReference(table.getName()); assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), tableFromUri.getServiceClient() .getCredentials().getClass().toString()); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java index 18e830fa096de..bc0ffae4ea82c 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java @@ -186,6 +186,39 @@ public static AccessCondition generateLeaseCondition(final String leaseID) { retCondition.leaseID = leaseID; return retCondition; } + + /** + * Returns an access condition such that an operation will be performed only if the resource exists on the service. + *

+ * Setting this access condition modifies the request to include the HTTP If-Match conditional header. + *

+ * For more information, see Specifying Conditional Headers + * for Blob Service Operations. + * + * @return An AccessCondition object that represents the if exists condition. + */ + public static AccessCondition generateIfExistsCondition() { + AccessCondition retCondition = new AccessCondition(); + retCondition.setIfMatch("*"); + return retCondition; + } + + /** + * Returns an access condition such that an operation will be performed only if the resource does not exist on the + * service. + *

+ * Setting this access condition modifies the request to include the HTTP If-None-Match conditional header. + *

+ * For more information, see Specifying Conditional Headers + * for Blob Service Operations. + * + * @return An AccessCondition object that represents the if not exists condition. + */ + public static AccessCondition generateIfNotExistsCondition() { + AccessCondition retCondition = new AccessCondition(); + retCondition.setIfNoneMatch("*"); + return retCondition; + } private String leaseID = null; @@ -223,7 +256,17 @@ public static AccessCondition generateLeaseCondition(final String leaseID) { * Represents the ifSequenceNumberEqual type. Used only for page blob operations. */ private Long ifSequenceNumberEqual = null; - + + /** + * Represents the ifMaxSizeLessThanOrEqual type. Used only for append blob operations. + */ + private Long ifMaxSizeLessThanOrEqual = null; + + /** + * Represents the ifAppendPositionEqual type. Used only for append blob operations. + */ + private Long ifAppendPositionEqual = null; + /** * Creates an instance of the AccessCondition class. */ @@ -297,6 +340,28 @@ public void applySourceConditionToRequest(final HttpURLConnection request) { } } + /** + * RESERVED FOR INTERNAL USE. Applies the access condition to the request. + * + * @param request + * A java.net.HttpURLConnection object that represents the request to which the condition is + * being applied. + * + * @throws StorageException + * If there is an error parsing the date value of the access condition. + */ + public void applyAppendConditionToRequest(final HttpURLConnection request) { + if (this.ifMaxSizeLessThanOrEqual != null) { + request.setRequestProperty(Constants.HeaderConstants.IF_MAX_SIZE_LESS_THAN_OR_EQUAL, + this.ifMaxSizeLessThanOrEqual.toString()); + } + + if (this.ifAppendPositionEqual != null) { + request.setRequestProperty(Constants.HeaderConstants.IF_APPEND_POSITION_EQUAL_HEADER, + this.ifAppendPositionEqual.toString()); + } + } + /** * RESERVED FOR INTERNAL USE. Applies the lease access condition to the request. * @@ -337,6 +402,17 @@ public void applySequenceConditionToRequest(final HttpURLConnection request) { } } + /** + * Gets the value for a conditional header used only for append operations. A number indicating the byte offset to check for. + * The append will succeed only if the end position is equal to this number. + * + * @return The append position number, or null if no condition exists. + */ + public Long getIfAppendPositionEqual() + { + return ifAppendPositionEqual; + } + /** * Gets the ETag when the If-Match condition is set. * @@ -346,6 +422,17 @@ public String getIfMatch() { return this.ifMatchETag; } + /** + * Gets the value for a conditional header used only for append operations. A number that indicates the maximum length in + * bytes to restrict the blob to when committing the block. + * + * @return The maximum size, or null if no condition exists. + */ + public Long getIfMaxSizeLessThanOrEqual() + { + return ifMaxSizeLessThanOrEqual; + } + /** * Gets the If-Modified-Since date. * @@ -412,6 +499,18 @@ public Long getIfSequenceNumberEqual() { return this.ifSequenceNumberEqual; } + /** + * Sets the value for a conditional header used only for append operations. A number indicating the byte offset to check for. + * The append will succeed only if the end position is equal to this number. + * + * @param ifAppendPositionEqual + * The append position number, or null if no condition exists. + */ + public void setIfAppendPositionEqual(Long ifAppendPositionEqual) + { + this.ifAppendPositionEqual = ifAppendPositionEqual; + } + /** * Sets the ETag for the If-Match condition. * @@ -421,6 +520,18 @@ public Long getIfSequenceNumberEqual() { public void setIfMatch(String etag) { this.ifMatchETag = normalizeEtag(etag); } + + /** + * Sets the value for a conditional header used only for append operations. A number that indicates the maximum length in + * bytes to restrict the blob to when committing the block. + * + * @param ifMaxSizeLessThanOrEqual + * The maximum size, or null if no condition exists. + */ + public void setIfMaxSizeLessThanOrEqual(Long ifMaxSizeLessThanOrEqual) + { + this.ifMaxSizeLessThanOrEqual = ifMaxSizeLessThanOrEqual; + } /** * Sets the If-Modified-Since date. diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java index a97a79025fa97..df3b347ea4192 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java @@ -29,6 +29,7 @@ import com.microsoft.azure.storage.core.StorageCredentialsHelper; import com.microsoft.azure.storage.core.Utility; import com.microsoft.azure.storage.file.CloudFileClient; +import com.microsoft.azure.storage.queue.CloudQueue; import com.microsoft.azure.storage.queue.CloudQueueClient; import com.microsoft.azure.storage.table.CloudTable; import com.microsoft.azure.storage.table.CloudTableClient; @@ -212,8 +213,8 @@ public static CloudStorageAccount getDevelopmentStorageAccount(final URI proxyUr * connection string format. *

* Note that while a connection string may include a SAS token, it is often easier to use the - * {@link CloudBlobContainer#CloudBlobContainer(URI)}, {@link CloudBlobContainer#CloudQueue(URI)}, - * {@link CloudTable#CloudBlobContainer(URI)} constructors directly. To do this, create a + * {@link CloudBlobContainer#CloudBlobContainer(URI)}, {@link CloudQueue#CloudQueue(URI)}, + * {@link CloudTable#CloudTable(URI)} constructors directly. To do this, create a * {@link StorageCredentialsSharedAccessSignature#StorageCredentialsSharedAccessSignature(String)} object with your * SAS token, use the {@link StorageCredentialsSharedAccessSignature#transformUri(URI)} method on the container, * queue, or table URI, and then use that URI to construct the object. @@ -241,12 +242,9 @@ public static CloudStorageAccount parse(final String connectionString) throws UR // 2 Validate General Settings rules, // - only setting value per key // - setting must have value. - // - One special case to this rule - the account key can be empty. for (final Entry entry : settings.entrySet()) { if (entry.getValue() == null || entry.getValue().equals(Constants.EMPTY_STRING)) { - if (!entry.getKey().equals(CloudStorageAccount.ACCOUNT_KEY_NAME)) { - throw new IllegalArgumentException(SR.INVALID_CONNECTION_STRING); - } + throw new IllegalArgumentException(SR.INVALID_CONNECTION_STRING); } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java index 65606ccba8c9a..49550ce2e8699 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -228,7 +228,17 @@ public static class HeaderConstants { * The format string for specifying ranges with only begin offset. */ public static final String BEGIN_RANGE_HEADER_FORMAT = "bytes=%d-"; - + + /** + * The format string for specifying the blob append offset. + */ + public static final String BLOB_APPEND_OFFSET = PREFIX_FOR_STORAGE_HEADER + "blob-append-offset"; + + /** + * The header that specifies committed block count. + */ + public static final String BLOB_COMMITTED_BLOCK_COUNT = PREFIX_FOR_STORAGE_HEADER + "blob-committed-block-count"; + /** * The header that specifies blob sequence number. */ @@ -350,10 +360,20 @@ public static class HeaderConstants { */ public static final int HTTP_UNUSED_306 = 306; + /** + * The blob append position equal header. + */ + public static final String IF_APPEND_POSITION_EQUAL_HEADER = PREFIX_FOR_STORAGE_HEADER + "blob-condition-appendpos"; + /** * The IfMatch header. */ public static final String IF_MATCH = "If-Match"; + + /** + * The blob maxsize condition header. + */ + public static final String IF_MAX_SIZE_LESS_THAN_OR_EQUAL = PREFIX_FOR_STORAGE_HEADER + "blob-condition-maxsize"; /** * The IfModifiedSince header. @@ -510,7 +530,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2014-02-14"; + public static final String TARGET_STORAGE_VERSION = "2015-02-21"; /** * The header that specifies the next visible time for a queue message. @@ -530,7 +550,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "2.2.0"; + public static final String USER_AGENT_VERSION = "3.0.0"; /** * The default type for content-type and accept @@ -673,6 +693,11 @@ public static class QueryConstants { */ public static final String START_ROW_KEY = "srk"; + /** + * The query component for stats. + */ + public static final String STATS = "stats"; + /** * The query component for delimiter. */ @@ -733,7 +758,7 @@ public static class QueryConstants { * XML element for an access policy. */ public static final String ACCESS_POLICY = "AccessPolicy"; - + /** * Buffer width used to copy data to output streams. */ @@ -899,11 +924,31 @@ public static class QueryConstants { */ public static final String LAST_MODIFIED_ELEMENT = "Last-Modified"; + /** + * Lease break period max in seconds. + */ + public static final int LEASE_BREAK_PERIOD_MAX = 60; + + /** + * Lease break period min in seconds. + */ + public static final int LEASE_BREAK_PERIOD_MIN = 0; + /** * XML element for the lease duration. */ public static final String LEASE_DURATION_ELEMENT = "LeaseDuration"; + /** + * Lease duration max in seconds. + */ + public static final int LEASE_DURATION_MAX = 60; + + /** + * Lease duration min in seconds. + */ + public static final int LEASE_DURATION_MIN = 15; + /** * XML element for the lease state. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Credentials.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Credentials.java index 4ae3f545e6d84..de9a849f36cfc 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Credentials.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Credentials.java @@ -19,10 +19,14 @@ import com.microsoft.azure.storage.core.Base64; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.Utility; /** * Represents the credentials used to sign a request against the storage services. + * + * @deprecated as of 3.0.0. Please use the equivalent methods on {@link StorageCredentialsAccountAndKey}. */ +@Deprecated public final class Credentials { /** * Stores the Account name for the credentials. @@ -32,7 +36,7 @@ public final class Credentials { /** * Stores the StorageKey for the credentials. */ - private final StorageKey key; + private StorageKey key; /** * Stores the name of the access key to be used when signing the request. @@ -51,12 +55,12 @@ public final class Credentials { * */ public Credentials(final String accountName, final byte[] key) { - if (accountName == null || accountName.length() == 0) { + if (Utility.isNullOrEmptyOrWhitespace(accountName)) { throw new IllegalArgumentException(SR.INVALID_ACCOUNT_NAME); } - if (key == null) { - throw new IllegalArgumentException(SR.KEY_NULL); + if (key == null || key.length == 0) { + throw new IllegalArgumentException(SR.INVALID_KEY); } this.accountName = accountName; @@ -75,7 +79,16 @@ public Credentials(final String accountName, final byte[] key) { * */ public Credentials(final String accountName, final String key) { - this(accountName, Base64.decode(key)); + if (Utility.isNullOrEmptyOrWhitespace(accountName)) { + throw new IllegalArgumentException(SR.INVALID_ACCOUNT_NAME); + } + + if (Utility.isNullOrEmptyOrWhitespace(key) || !Base64.validateIsBase64String(key)) { + throw new IllegalArgumentException(SR.INVALID_KEY); + } + + this.accountName = accountName; + this.key = new StorageKey(Base64.decode(key)); } /** @@ -133,12 +146,12 @@ protected void setAccountName(final String accountName) { } /** - * Sets the name of the access key to be used when signing the request. + * Sets the access key to be used when signing the request. * * @param keyName * A String that represents the name of the access key to be used when signing the request. */ - protected void setKeyName(final String keyName) { - this.keyName = keyName; + protected void setKey(final StorageKey key) { + this.key = key; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java index 5af70e64fc11d..d35b8cba349d0 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java @@ -237,7 +237,7 @@ public ArrayList getRequestResults() { * manner. * * @param requestResult - * A {@Link RequestResult} to append. + * A {@link RequestResult} to append. */ public synchronized void appendRequestResult(RequestResult requestResult) { this.requestResults.add(requestResult); @@ -485,7 +485,7 @@ public void setRetryingEventHandler( /** * Indicates whether the client library should produce log entries by default. The default can be overridden - * to enable logging for an individual operation context instance by using {@link setLoggingEnabled}. + * to enable logging for an individual operation context instance by using {@link #setLoggingEnabled}. * * @return * true if logging is enabled by default; otherwise false. @@ -496,10 +496,11 @@ public static boolean isLoggingEnabledByDefault() { /** * Specifies whether the client library should produce log entries by default. The default can be overridden - * to turn on logging for an individual operation context instance by using {@link setLoggingEnabled}. + * to turn on logging for an individual operation context instance by using {@link #setLoggingEnabled}. * * @param enableLoggingByDefault - * true if logging should be enabled by default; otherwise false if logging should be disabled by default. + * true if logging should be enabled by default; otherwise false if logging should + * be disabled by default. */ public static void setLoggingEnabledByDefault(boolean enableLoggingByDefault) { OperationContext.enableLoggingByDefault = enableLoggingByDefault; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java index 9abd95805cfc7..2b458e51aca4a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java @@ -78,10 +78,10 @@ public RequestOptions(final RequestOptions other) { /** * Populates the default timeout, retry policy, and location mode from client if they are null. * - * @param options + * @param modifiedOptions * The input options to copy from when applying defaults */ - protected static final RequestOptions applyBaseDefaultsInternal(final RequestOptions modifiedOptions) { + protected static void applyBaseDefaultsInternal(final RequestOptions modifiedOptions) { Utility.assertNotNull("modifiedOptions", modifiedOptions); if (modifiedOptions.getRetryPolicyFactory() == null) { modifiedOptions.setRetryPolicyFactory(new RetryExponentialRetry()); @@ -90,14 +90,12 @@ protected static final RequestOptions applyBaseDefaultsInternal(final RequestOpt if (modifiedOptions.getLocationMode() == null) { modifiedOptions.setLocationMode(LocationMode.PRIMARY_ONLY); } - - return modifiedOptions; } /** * Populates any null fields in the first requestOptions object with values from the second requestOptions object. */ - protected static final RequestOptions populateRequestOptions(RequestOptions modifiedOptions, + protected static void populateRequestOptions(RequestOptions modifiedOptions, final RequestOptions clientOptions, final boolean setStartTime) { if (modifiedOptions.getRetryPolicyFactory() == null) { modifiedOptions.setRetryPolicyFactory(clientOptions.getRetryPolicyFactory()); @@ -120,8 +118,6 @@ protected static final RequestOptions populateRequestOptions(RequestOptions modi modifiedOptions.setOperationExpiryTimeInMs(new Date().getTime() + modifiedOptions.getMaximumExecutionTimeInMs()); } - - return modifiedOptions; } /** @@ -214,7 +210,7 @@ public final void setRetryPolicyFactory(final RetryPolicyFactory retryPolicyFact * {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service * client will use that server timeout. * - * @param timeoutInMs + * @param timeoutIntervalInMs * The timeout, in milliseconds, to use for this request. */ public final void setTimeoutIntervalInMs(final Integer timeoutIntervalInMs) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java index d32bf370df4ed..82065d1a8490a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java @@ -115,10 +115,11 @@ public RetryInfo evaluate(RetryContext retryContext, OperationContext operationC boolean secondaryNotFound = this.evaluateLastAttemptAndSecondaryNotFound(retryContext); if (retryContext.getCurrentRetryCount() < this.maximumAttempts) { - if ((!secondaryNotFound && retryContext.getLastRequestResult().getStatusCode() >= 400 && retryContext - .getLastRequestResult().getStatusCode() < 500) - || retryContext.getLastRequestResult().getStatusCode() == HttpURLConnection.HTTP_NOT_IMPLEMENTED - || retryContext.getLastRequestResult().getStatusCode() == HttpURLConnection.HTTP_VERSION) { + int statusCode = retryContext.getLastRequestResult().getStatusCode(); + if ((!secondaryNotFound && statusCode >= 400 && statusCode < 500) + || statusCode == HttpURLConnection.HTTP_NOT_IMPLEMENTED + || statusCode == HttpURLConnection.HTTP_VERSION + || statusCode == Constants.HeaderConstants.HTTP_UNUSED_306) { return null; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java index 3c32552306452..474666db698eb 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java @@ -77,10 +77,11 @@ public RetryInfo evaluate(RetryContext retryContext, OperationContext operationC boolean secondaryNotFound = this.evaluateLastAttemptAndSecondaryNotFound(retryContext); if (retryContext.getCurrentRetryCount() < this.maximumAttempts) { - if ((!secondaryNotFound && retryContext.getLastRequestResult().getStatusCode() >= 400 && retryContext - .getLastRequestResult().getStatusCode() < 500) - || retryContext.getLastRequestResult().getStatusCode() == HttpURLConnection.HTTP_NOT_IMPLEMENTED - || retryContext.getLastRequestResult().getStatusCode() == HttpURLConnection.HTTP_VERSION) { + int statusCode = retryContext.getLastRequestResult().getStatusCode(); + if ((!secondaryNotFound && statusCode >= 400 && statusCode < 500) + || statusCode == HttpURLConnection.HTTP_NOT_IMPLEMENTED + || statusCode == HttpURLConnection.HTTP_VERSION + || statusCode == Constants.HeaderConstants.HTTP_UNUSED_306) { return null; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryingEvent.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryingEvent.java index 2e16b749a91c5..47f4299a274c8 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryingEvent.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryingEvent.java @@ -37,8 +37,9 @@ public final class RetryingEvent extends BaseEvent { * a connection object. * @param requestResult * A {@link RequestResult} object that represents the current request result. - * @param retryCount - * The number of retries done for this request (including the pending retry). + * @param retryContext + * A {@link RetryContext} object which contains the number of retries done for this request (including + * the pending retry) and other retry information. */ public RetryingEvent(OperationContext opContext, Object connectionObject, RequestResult requestResult, RetryContext retryContext) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java index a4c6c999b80d3..f5338be3f41fc 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java @@ -48,12 +48,6 @@ public abstract class ServiceClient { */ private boolean usePathStyleUris; - /** - * Holds the AuthenticationScheme associated with this Service Client. - */ - @SuppressWarnings("deprecation") - protected AuthenticationScheme authenticationScheme = AuthenticationScheme.SHAREDKEYFULL; - /** * Creates an instance of the ServiceClient class using the specified service endpoint and account * credentials. @@ -89,10 +83,10 @@ public HttpURLConnection buildRequest(ServiceClient client, Void parentObject, O public void signRequest(HttpURLConnection connection, ServiceClient client, OperationContext context) throws Exception { if (signAsTable) { - StorageRequest.signTableRequest(connection, client, -1, null); + StorageRequest.signTableRequest(connection, client, -1, context); } else { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1, context); } } @@ -138,10 +132,10 @@ public HttpURLConnection buildRequest(ServiceClient client, Void parentObject, O public void signRequest(HttpURLConnection connection, ServiceClient client, OperationContext context) throws Exception { if (signAsTable) { - StorageRequest.signTableRequest(connection, client, -1, null); + StorageRequest.signTableRequest(connection, client, -1, context); } else { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1, context); } } @@ -175,18 +169,6 @@ public final StorageCredentials getCredentials() { return this.credentials; } - /** - * Returns the AuthenticationScheme associated with this service client. - * - * @return An {@link AuthenticationScheme} object which represents the authentication scheme associated with this - * client. - * - * @deprecated as of 2.0.0. In the future only SharedKeyFull will be used. - */ - public final AuthenticationScheme getAuthenticationScheme() { - return this.authenticationScheme; - } - /** * Returns the base URI for this service client. * @@ -234,19 +216,6 @@ protected final void setStorageUri(final StorageUri storageUri) { this.storageUri = storageUri; } - /** - * Sets the Authentication Scheme to use with this service client. - * - * @param scheme - * An {@link AuthenticationScheme} object which represents the authentication scheme being assigned for - * the service client. - * - * @deprecated as of 2.0.0. In the future, only SharedKeyFull will be used. - */ - public final void setAuthenticationScheme(final AuthenticationScheme scheme) { - this.authenticationScheme = scheme; - } - protected StorageRequest uploadServicePropertiesImpl(final ServiceProperties properties, final RequestOptions options, final OperationContext opContext, final boolean signAsTable) throws StorageException { @@ -277,10 +246,10 @@ public void setHeaders(HttpURLConnection connection, Void parentObject, Operatio public void signRequest(HttpURLConnection connection, ServiceClient client, OperationContext context) throws Exception { if (signAsTable) { - StorageRequest.signTableRequest(connection, client, descriptor.getLength(), null); + StorageRequest.signTableRequest(connection, client, descriptor.getLength(), context); } else { - StorageRequest.signBlobQueueAndFileRequest(connection, client, descriptor.getLength(), null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, descriptor.getLength(), context); } } @@ -321,131 +290,6 @@ public void recoveryAction(OperationContext context) throws IOException { } } - /** - * Gets the default location mode for requests made via the service client. - * - * @return A {@link LocationMode} object which represents the default location mode for the service client. - * - * @deprecated use {@link #getDefaultRequestOptions().getLocationMode()} instead. - */ - @Deprecated - public final LocationMode getLocationMode() { - return this.getDefaultRequestOptions().getLocationMode(); - } - - /** - * Returns the retry policy currently in effect for this service client. - * - * @return An {@link RetryPolicyFactory} object which represents the current retry policy. - * - * @see RetryPolicy - * @see RetryExponentialRetry - * @see RetryLinearRetry - * @see RetryNoRetry - * - * @deprecated use {@link #getDefaultRequestOptions().getRetryPolicyFactory()} instead. - */ - @Deprecated - public final RetryPolicyFactory getRetryPolicyFactory() { - return this.getDefaultRequestOptions().getRetryPolicyFactory(); - } - - /** - * Returns the timeout value for requests made to the service. For more information about the timeout, see - * {@link #setTimeoutInMs}. - * - * @return The current timeout value, in milliseconds, for requests made to the storage service. - * - * @deprecated use {@link #getDefaultRequestOptions().getTimeoutIntervalInMs()} instead. - */ - @Deprecated - public final int getTimeoutInMs() { - return this.getDefaultRequestOptions().getTimeoutIntervalInMs(); - } - - /** - * Returns the maximum execution time, in milliseconds, across all potential retries. For more information about - * maximum execution time, see {@link #setMaximumExecutionTimeInMs(Integer)}. - * - * @return The maximum execution time, in milliseconds, for requests made to the storage service. - * - * @deprecated use {@link #getDefaultRequestOptions().getMaximumExecutionTimeInMs()} instead. - */ - @Deprecated - public Integer getMaximumExecutionTimeInMs() { - return this.getDefaultRequestOptions().getMaximumExecutionTimeInMs(); - } - - /** - * Sets the default {@link LocationMode} for requests made via the service client. - * - * @param locationMode - * the locationMode to set - * - * @deprecated use {@link #getDefaultRequestOptions().setLocationMode()} instead. - */ - @Deprecated - public void setLocationMode(LocationMode locationMode) { - this.getDefaultRequestOptions().setLocationMode(locationMode); - } - - /** - * Sets the RetryPolicyFactory object to use when making service requests. - * - * @param retryPolicyFactory - * the RetryPolicyFactory object to use when making service requests. - * - * @deprecated use {@link #getDefaultRequestOptions().setRetryPolicyFactory()} instead. - */ - @Deprecated - public void setRetryPolicyFactory(final RetryPolicyFactory retryPolicyFactory) { - this.getDefaultRequestOptions().setRetryPolicyFactory(retryPolicyFactory); - } - - /** - * Sets the timeout to use when making requests to the storage service. - *

- * The server timeout interval begins at the time that the complete request has been received by the service, and - * the server begins processing the response. If the timeout interval elapses before the response is returned to the - * client, the operation times out. The timeout interval resets with each retry, if the request is retried. - * - * You can change this value on the service client by setting this property, so that all subsequent requests made - * via the service client will use the new timeout interval. You can also change this value for an individual - * request, by setting the {@link RequestOptions#setTimeoutIntervalInMs(Integer)} property. - * - * @param timeoutInMs - * The timeout, in milliseconds, to use when making requests to the storage service. - * - * @deprecated use {@link #getDefaultRequestOptions().setTimeoutIntervalInMs()} instead. - */ - @Deprecated - public final void setTimeoutInMs(final int timeoutInMs) { - this.getDefaultRequestOptions().setTimeoutIntervalInMs(timeoutInMs); - } - - /** - * Sets the maximum execution time to use when making requests to the storage service. - *

- * The maximum execution time interval begins at the time that the client begins building the request. The maximum - * execution time is checked intermittently while uploading data, downloading data, and before executing retries. - * The service will continue to upload, download, and retry until the maximum execution time is reached. At that - * time, any partial uploads or downloads will be cancelled and an exception will be thrown. - * - * The default maximum execution time is null, indicating no maximum time. You can change this value on the service - * client by setting this property, so that all subsequent requests made via the service client will use the new - * maximum execution time. You can also change this value for an individual request, by setting the - * {@link RequestOptions#setMaximumExecutionTimeInMs(Integer)} property. - * - * @param maximumExecutionTimeInMs - * The maximum execution time, in milliseconds, to use when making service requests. - * - * @deprecated use {@link #getDefaultRequestOptions().setMaximumExecutionTimeInMs()} instead. - */ - @Deprecated - public void setMaximumExecutionTimeInMs(Integer maximumExecutionTimeInMs) { - this.getDefaultRequestOptions().setMaximumExecutionTimeInMs(maximumExecutionTimeInMs); - } - /** * Gets the {@link RequestOptions} that is used for requests associated with this ServiceClient * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceProperties.java index 84cd8c80b6f9d..3c8d3d26c7e1b 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceProperties.java @@ -124,7 +124,7 @@ public CorsProperties getCors() { /** * Sets the Cross-Origin Resource Sharing (CORS) properties. * - * @param CORS + * @param cors * A {@link CorsProperties} object which represents the CORS properties. */ public void setCors(final CorsProperties cors) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessHeaders.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessHeaders.java new file mode 100644 index 0000000000000..47c674a4fd90c --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessHeaders.java @@ -0,0 +1,164 @@ +/** + * Copyright 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.azure.storage; + +import com.microsoft.azure.storage.core.Utility; + +/** + * RESERVED FOR INTERNAL USE. Represents the optional headers that can be returned using SAS. + */ +public abstract class SharedAccessHeaders { + /** + * The cache-control header returned. + */ + private String cacheControl; + + /** + * The content-disposition header returned. + */ + private String contentDisposition; + + /** + * The content-encoding header returned. + */ + private String contentEncoding; + + /** + * The content-language header returned. + */ + private String contentLanguage; + + /** + * The content-type header returned. + */ + private String contentType; + + /** + * Initializes a new instance of the {@link SharedAccessHeaders} class. + */ + public SharedAccessHeaders() { + } + + /** + * Initializes a new instance of the {@link SharedAccessHeaders} class based on an existing instance. + * + * @param other + * A {@link SharedAccessHeaders} object which specifies the set of properties to clone. + */ + public SharedAccessHeaders(SharedAccessHeaders other) { + Utility.assertNotNull("other", other); + + this.contentType = other.contentType; + this.contentDisposition = other.contentDisposition; + this.contentEncoding = other.contentEncoding; + this.contentLanguage = other.contentLanguage; + this.cacheControl = other.cacheControl; + } + + /** + * Gets the cache control header. + * + * @return A String which represents the cache control header. + */ + public String getCacheControl() { + return this.cacheControl; + } + + /** + * Sets the cache control header. + * + * @param cacheControl + * A String which specifies the cache control header. + */ + public void setCacheControl(String cacheControl) { + this.cacheControl = cacheControl; + } + + /** + * Gets the content disposition header. + * + * @return A String which represents the content disposition header. + */ + public String getContentDisposition() { + return this.contentDisposition; + } + + /** + * Sets the content disposition header. + * + * @param contentDisposition + * A String which specifies the content disposition header. + */ + public void setContentDisposition(String contentDisposition) { + this.contentDisposition = contentDisposition; + } + + /** + * Gets the content encoding header. + * + * @return A String which represents the content encoding header. + */ + public String getContentEncoding() { + return this.contentEncoding; + } + + /** + * Sets the content encoding header. + * + * @param contentEncoding + * A String which specifies the content encoding header. + */ + public void setContentEncoding(String contentEncoding) { + this.contentEncoding = contentEncoding; + } + + /** + * Gets the content language header. + * + * @return A String which represents the content language header. + */ + public String getContentLanguage() { + return this.contentLanguage; + } + + /** + * Sets the content language header. + * + * @param contentLanguage + * A String which specifies the content language header. + */ + public void setContentLanguage(String contentLanguage) { + this.contentLanguage = contentLanguage; + } + + /** + * Gets the content type header. + * + * @return A String which represents the content type header. + */ + public String getContentType() { + return this.contentType; + } + + /** + * Sets the content type header. + * + * @param contentType + * A String which specifies the content type header. + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } +} diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsAccountAndKey.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsAccountAndKey.java index 13a23191d2220..85c4ab8d8bcae 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsAccountAndKey.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsAccountAndKey.java @@ -17,6 +17,8 @@ import java.net.URI; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.Utility; /** * Represents storage account credentials, based on storage account and access key, for accessing the Microsoft Azure @@ -27,6 +29,7 @@ public final class StorageCredentialsAccountAndKey extends StorageCredentials { /** * The internal Credentials associated with the StorageCredentials. */ + @SuppressWarnings("deprecation") private Credentials credentials; /** @@ -38,6 +41,7 @@ public final class StorageCredentialsAccountAndKey extends StorageCredentials { * @param key * An array of bytes that represent the account access key. */ + @SuppressWarnings("deprecation") public StorageCredentialsAccountAndKey(final String accountName, final byte[] key) { this.credentials = new Credentials(accountName, key); } @@ -51,23 +55,88 @@ public StorageCredentialsAccountAndKey(final String accountName, final byte[] ke * @param key * A String that represents the Base-64-encoded account access key. */ + @SuppressWarnings("deprecation") public StorageCredentialsAccountAndKey(final String accountName, final String key) { - this(accountName, Base64.decode(key)); + this.credentials = new Credentials(accountName, key); } /** - * Returns the associated account name for the credentials. + * Gets the account name. * - * @return A String that contains the account name for the credentials. + * @return A String that contains the account name. */ + @SuppressWarnings("deprecation") @Override public String getAccountName() { return this.credentials.getAccountName(); } + + /** + * Exports the value of the access key to a Base64-encoded string. + * + * @return A String that represents the Base64-encoded access key. + */ + @SuppressWarnings("deprecation") + public String exportBase64EncodedKey() { + return this.credentials.getKey().getBase64EncodedKey(); + } + + /** + * Exports the value of the access key to an array of bytes. + * + * @return A byte array that represents the access key. + */ + @SuppressWarnings("deprecation") + public byte[] exportKey() { + return this.credentials.getKey().getKey(); + } + + /** + * Sets the account name. + * + * @param accountName + * A String that contains the account name. + */ + @SuppressWarnings("deprecation") + public void setAccountName(String accountName) { + this.credentials.setAccountName(accountName); + } + + /** + * Sets the name of the access key to be used when signing the request. + * + * @param key + * A String that represents the name of the access key to be used when signing the request. + */ + @SuppressWarnings("deprecation") + public void updateKey(final String key) { + if (Utility.isNullOrEmptyOrWhitespace(key) || Base64.validateIsBase64String(key)) { + throw new IllegalArgumentException(SR.INVALID_KEY); + } + + this.credentials.setKey(new StorageKey(Base64.decode(key))); + } + + /** + * Sets the name of the access key to be used when signing the request. + * + * @param key + * A String that represents the name of the access key to be used when signing the request. + */ + @SuppressWarnings("deprecation") + public void updateKey(final byte[] key) { + if (key == null || key.length == 0) { + throw new IllegalArgumentException(SR.INVALID_KEY); + } + + this.credentials.setKey(new StorageKey(key)); + } /** * Gets the name of the key used by these credentials. + * @deprecated as of 3.0.0. The key name property is only useful internally. */ + @Deprecated public String getAccountKeyName() { return this.credentials.getKeyName(); } @@ -77,7 +146,10 @@ public String getAccountKeyName() { * * @return A Credentials object that contains the internal credentials associated with this instance of * the StorageCredentialsAccountAndKey class. + * @deprecated as of 3.0.0. Please use {@link #getAccountName()}, {@link #exportKey()}, or + * {@link #exportBase64EncodedKey()} */ + @Deprecated public Credentials getCredentials() { return this.credentials; } @@ -88,7 +160,10 @@ public Credentials getCredentials() { * @param credentials * A Credentials object that represents the credentials to set for this instance of the * StorageCredentialsAccountAndKey class. + * @deprecated as of 3.0.0. Please use {@link #setAccountName(String)}, {@link #updateKey(String)}, or + * {@link #updateKey(byte[])} */ + @Deprecated public void setCredentials(final Credentials credentials) { this.credentials = credentials; } @@ -101,6 +176,7 @@ public void setCredentials(final Credentials credentials) { * * @return A String that represents this object, optionally including sensitive data. */ + @SuppressWarnings("deprecation") @Override public String toString(final boolean exportSecrets) { return String.format("%s=%s;%s=%s", CloudStorageAccount.ACCOUNT_NAME_NAME, this.getAccountName(), diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java index 98e72e6e0e38d..214ec545a45fd 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java @@ -174,6 +174,11 @@ public final class StorageErrorCodeStrings { */ public static final String INVALID_AUTHENTICATION_INFO = "InvalidAuthenticationInfo"; + /** + * Error code that may be returned when the specified append offset is invalid. + */ + public static final String INVALID_APPEND_POSITION = "AppendPositionConditionNotMet"; + /** * An incorrect blob type was specified. */ @@ -214,6 +219,11 @@ public final class StorageErrorCodeStrings { */ public static final String INVALID_MARKER = "InvalidMarker"; + /** + * Error code that may be returned when the specified max blob size is exceeded. + */ + public static final String INVALID_MAX_BLOB_SIZE_CONDITION = "MaxBlobSizeConditionNotMet"; + /** * The specified MD5 hash is invalid. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageEventMultiCaster.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageEventMultiCaster.java index 890d2ff010016..a66df16993e2f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageEventMultiCaster.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageEventMultiCaster.java @@ -21,7 +21,7 @@ * * @param * An object that represents the type of the event. - * @param + * @param * An object that represents the type of the event listener. */ public final class StorageEventMultiCaster> { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java index d466cb1465596..5f39729feb2e5 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java @@ -111,9 +111,6 @@ public static StorageException translateException(final StorageRequest * The HTTP status code returned by the operation. * @param statusDescription * A String that represents the status description. - * @param details - * A {@link StorageExtendedErrorInformation} object that represents the error details returned by the - * operation. * @param inner * An Exception object that represents a reference to the initial exception, if one exists. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageKey.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageKey.java index 4aa950c0e6e2f..7188a819467c9 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageKey.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageKey.java @@ -26,7 +26,9 @@ /** * Represents a container for a storage key. + * @deprecated as of 3.0.0. Please use the methods on {@link StorageCredentialsAccountAndKey}. */ +@Deprecated public final class StorageKey { /** * Computes a signature for the specified string using the HMAC-SHA256 algorithm. diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/analytics/CloudAnalyticsClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/analytics/CloudAnalyticsClient.java index df92a0bb91dfe..32d9690355b41 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/analytics/CloudAnalyticsClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/analytics/CloudAnalyticsClient.java @@ -361,7 +361,7 @@ public Iterable listLogRecords(StorageService service, Date startTime * Returns an enumerable collection of log records, retrieved lazily. * * @param logBlobs - * A {@link Iterable} of blobs to parse LogRecords from. + * A {@link Iterable} of blobs to parse LogRecords from. * @return * An enumerable collection of objects that implement {@link ListBlobItem} and are retrieved lazily. * @throws StorageException diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index 5c97b2dffc12e..cb9040519093e 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -20,6 +20,12 @@ * Holds the Constants used for the Blob Service. */ final class BlobConstants { + + /** + * Specifies the append blob type. + */ + public static final String APPEND_BLOB = "AppendBlob"; + /** * XML element for authentication error details. */ @@ -63,12 +69,7 @@ final class BlobConstants { * Specifies the block blob type. */ public static final String BLOCK_BLOB = "BlockBlob"; - - /** - * Constant signaling a block blob. - */ - public static final String BLOCK_BLOB_VALUE = "BlockBlob"; - + /** * XML element for blocks. */ @@ -171,11 +172,6 @@ final class BlobConstants { */ public static final String PAGE_BLOB = "PageBlob"; - /** - * Constant signaling a page blob. - */ - public static final String PAGE_BLOB_VALUE = "PageBlob"; - /** * XML element for page list elements. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java index d2fe31a609d17..e84209155a2a5 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java @@ -54,8 +54,9 @@ public final class BlobContainerProperties { * The ETag value is a unique identifier that is updated when a write operation is performed against the container. * It may be used to perform operations conditionally, providing concurrency control and improved efficiency. *

- * The {@link AccessCondition#ifMatch} and {@link AccessCondition#ifNoneMatch} methods take an ETag value and return - * an {@link AccessCondition} object that may be specified on the request. + * The {@link AccessCondition#generateIfMatchCondition(String)} and + * {@link AccessCondition#generateIfNoneMatchCondition(String)} methods take an ETag value and return an + * {@link AccessCondition} object that may be specified on the request. * * @return A String which represents the ETag. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java index 7917d6b9c932d..c1ada37373c71 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java @@ -117,6 +117,9 @@ public void endElement(String uri, String localName, String qName) throws SAXExc else if (this.properties.getBlobType() == BlobType.PAGE_BLOB) { retBlob = this.container.getPageBlobReference(this.blobName); } + else if (this.properties.getBlobType() == BlobType.APPEND_BLOB) { + retBlob = this.container.getAppendBlobReference(this.blobName); + } else { throw new SAXException(SR.INVALID_RESPONSE_RECEIVED); } @@ -229,12 +232,15 @@ else if (Constants.HeaderConstants.CONTENT_DISPOSITION.equals(currentNode)) { } else if (BlobConstants.BLOB_TYPE_ELEMENT.equals(currentNode)) { final String tempString = value; - if (tempString.equals(BlobConstants.BLOCK_BLOB_VALUE)) { + if (tempString.equals(BlobConstants.BLOCK_BLOB)) { this.properties.setBlobType(BlobType.BLOCK_BLOB); } - else if (tempString.equals(BlobConstants.PAGE_BLOB_VALUE.toString())) { + else if (tempString.equals(BlobConstants.PAGE_BLOB.toString())) { this.properties.setBlobType(BlobType.PAGE_BLOB); } + else if (tempString.equals(BlobConstants.APPEND_BLOB.toString())) { + this.properties.setBlobType(BlobType.APPEND_BLOB); + } else { throw new SAXException(SR.INVALID_RESPONSE_RECEIVED); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java index 7d732333d941f..f7b31c8a99f98 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java @@ -19,23 +19,31 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Random; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.Logger; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.Utility; @@ -43,45 +51,43 @@ * The class is an append-only stream for writing into storage. */ public final class BlobOutputStream extends OutputStream { - /** - * Holds the random number generator used to create starting blockIDs. - */ - private static Random blockSequenceGenerator = new Random(); /** - * Holds the reference to the blob this stream is associated with. + * Holds the {@link AccessCondition} object that represents the access conditions for the blob. */ - private final CloudBlob parentBlobRef; + private AccessCondition accessCondition; /** - * Determines if this stream is used against a page blob or block blob. + * Used for block blobs, holds the block id prefix. */ - private BlobType streamType = BlobType.UNSPECIFIED; + private String blockIdPrefix; /** - * A flag to determine if the stream is faulted, if so the lasterror will be thrown on next operation. + * Used for block blobs, holds the block list. */ - volatile boolean streamFaulted; + private ArrayList blockList; /** - * Holds the lock for synchronized access to the last error. + * The CompletionService used to await task completion for this stream. */ - Object lastErrorLock = new Object(); - + private final ExecutorCompletionService completionService; + /** - * Holds the last exception this stream encountered. + * Holds the futures of the executing tasks. The starting size of the set is a multiple of the concurrent request + * count to reduce the cost of resizing the set later. */ - IOException lastError; + private final Set> futureSet; /** - * Holds the OperationContext for the current stream. + * Holds the write threshold of number of bytes to buffer prior to dispatching a write. For block blob this is the + * block size, for page blob this is the Page commit size. */ - OperationContext opContext; - + private int internalWriteThreshold = -1; + /** - * Holds the options for the current stream. + * Holds the last exception this stream encountered. */ - BlobRequestOptions options; + private volatile IOException lastError = null; /** * Holds the reference to the MD5 digest for the blob. @@ -89,55 +95,37 @@ public final class BlobOutputStream extends OutputStream { private MessageDigest md5Digest; /** - * Used for block blobs, holds the current BlockID Sequence number. + * Holds the OperationContext for the current stream. */ - private long blockIdSequenceNumber = -1; + private final OperationContext opContext; /** - * Used for block blobs, holds the block list. + * Holds the options for the current stream. */ - private ArrayList blockList; + private final BlobRequestOptions options; + - /** - * Used for page blobs, holds the currentOffset the stream is writing to. - */ - private long currentPageOffset; + private long currentBlobOffset; /** * A private buffer to store data prior to committing to the cloud. */ - private ByteArrayOutputStream outBuffer; + private volatile ByteArrayOutputStream outBuffer; /** - * Holds the number of currently buffered bytes. - */ - private int currentBufferedBytes; - - /** - * Holds the write threshold of number of bytes to buffer prior to dispatching a write. For block blob this is the - * block size, for page blob this is the Page commit size. + * Holds the reference to the blob this stream is associated with. */ - private int internalWriteThreshold = -1; - + private final CloudBlob parentBlobRef; + /** - * Holds the number of current outstanding requests. + * Determines if this stream is used against a page blob or block blob. */ - private volatile int outstandingRequests; + private BlobType streamType = BlobType.UNSPECIFIED; /** * The ExecutorService used to schedule tasks for this stream. */ - private final ExecutorService threadExecutor; - - /** - * The CompletionService used to await task completion for this stream. - */ - private final ExecutorCompletionService completionService; - - /** - * Holds the {@link AccessCondition} object that represents the access conditions for the blob. - */ - AccessCondition accessCondition = null; + private final ThreadPoolExecutor threadExecutor; /** * Initializes a new instance of the BlobOutputStream class. @@ -154,7 +142,7 @@ public final class BlobOutputStream extends OutputStream { * @throws StorageException * An exception representing any error which occurred during the operation. */ - protected BlobOutputStream(final CloudBlob parentBlob, final AccessCondition accessCondition, + private BlobOutputStream(final CloudBlob parentBlob, final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) throws StorageException { this.accessCondition = accessCondition; this.parentBlobRef = parentBlob; @@ -162,11 +150,13 @@ protected BlobOutputStream(final CloudBlob parentBlob, final AccessCondition acc this.options = new BlobRequestOptions(options); this.outBuffer = new ByteArrayOutputStream(); this.opContext = opContext; - this.streamFaulted = false; if (this.options.getConcurrentRequestCount() < 1) { throw new IllegalArgumentException("ConcurrentRequestCount"); } + + this.futureSet = Collections.newSetFromMap(new ConcurrentHashMap, Boolean>( + this.options.getConcurrentRequestCount() == null ? 1 : this.options.getConcurrentRequestCount() * 2)); if (this.options.getStoreBlobContentMD5()) { try { @@ -178,8 +168,13 @@ protected BlobOutputStream(final CloudBlob parentBlob, final AccessCondition acc } } - // V2 cachedThreadPool for perf. - this.threadExecutor = Executors.newFixedThreadPool(this.options.getConcurrentRequestCount()); + // V2 cachedThreadPool for perf. + this.threadExecutor = new ThreadPoolExecutor( + this.options.getConcurrentRequestCount(), + this.options.getConcurrentRequestCount(), + 10, + TimeUnit.SECONDS, + new LinkedBlockingQueue()); this.completionService = new ExecutorCompletionService(this.threadExecutor); } @@ -201,10 +196,10 @@ protected BlobOutputStream(final CloudBlob parentBlob, final AccessCondition acc protected BlobOutputStream(final CloudBlockBlob parentBlob, final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) throws StorageException { this((CloudBlob) parentBlob, accessCondition, options, opContext); - this.blockIdSequenceNumber = (long) (blockSequenceGenerator.nextInt(Integer.MAX_VALUE)) - + blockSequenceGenerator.nextInt(Integer.MAX_VALUE - 100000); - this.blockList = new ArrayList(); + this.blockList = new ArrayList(); + this.blockIdPrefix = UUID.randomUUID().toString() + "-"; + this.streamType = BlobType.BLOCK_BLOB; this.internalWriteThreshold = this.parentBlobRef.getStreamWriteSizeInBytes(); } @@ -213,7 +208,7 @@ protected BlobOutputStream(final CloudBlockBlob parentBlob, final AccessConditio * Initializes a new instance of the BlobOutputStream class for a CloudPageBlob * * @param parentBlob - * A {@link CloudBlockBlob} object which represents the blob that this stream is associated with. + * A {@link CloudPageBlob} object which represents the blob that this stream is associated with. * @param length * A long which represents the length of the page blob in bytes, which must be a multiple of * 512. @@ -233,8 +228,44 @@ protected BlobOutputStream(final CloudPageBlob parentBlob, final long length, throws StorageException { this(parentBlob, accessCondition, options, opContext); this.streamType = BlobType.PAGE_BLOB; + this.internalWriteThreshold = (int) Math.min(this.parentBlobRef.getStreamWriteSizeInBytes(), length); } + + /** + * Initializes a new instance of the BlobOutputStream class for a CloudAppendBlob + * + * @param parentBlob + * A {@link CloudAppendBlob} object which represents the blob that this stream is associated with. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object which specifies any additional options for the request + * @param opContext + * An {@link OperationContext} object which is used to track the execution of the operation + * + * @throws StorageException + * An exception representing any error which occurred during the operation. + */ + @DoesServiceRequest + protected BlobOutputStream(final CloudAppendBlob parentBlob, final AccessCondition accessCondition, + final BlobRequestOptions options, final OperationContext opContext) + throws StorageException { + this((CloudBlob)parentBlob, accessCondition, options, opContext); + this.streamType = BlobType.APPEND_BLOB; + + this.accessCondition = accessCondition != null ? accessCondition : new AccessCondition(); + if (this.accessCondition.getIfAppendPositionEqual() != null) { + this.currentBlobOffset = this.accessCondition.getIfAppendPositionEqual(); + } + else { + // If this is an existing blob, we've done a downloadProperties to get the length + // If this is a new blob, getLength will correctly return 0 + this.currentBlobOffset = parentBlob.getProperties().getLength(); + } + + this.internalWriteThreshold = this.parentBlobRef.getStreamWriteSizeInBytes(); + } /** * Helper function to check if the stream is faulted, if it is it surfaces the exception. @@ -244,10 +275,8 @@ protected BlobOutputStream(final CloudPageBlob parentBlob, final long length, * closed. */ private void checkStreamState() throws IOException { - synchronized (this.lastErrorLock) { - if (this.streamFaulted) { - throw this.lastError; - } + if (this.lastError != null) { + throw this.lastError; } } @@ -260,7 +289,7 @@ private void checkStreamState() throws IOException { */ @Override @DoesServiceRequest - public void close() throws IOException { + public synchronized void close() throws IOException { try { // if the user has already closed the stream, this will throw a STREAM_CLOSED exception // if an exception was thrown by any thread in the threadExecutor, realize it now @@ -282,10 +311,7 @@ public void close() throws IOException { } finally { // if close() is called again, an exception will be thrown - synchronized (this.lastErrorLock) { - this.streamFaulted = true; - this.lastError = new IOException(SR.STREAM_CLOSED); - } + this.lastError = new IOException(SR.STREAM_CLOSED); // if an exception was thrown and the executor was not yet closed, call shutDownNow() to cancel all tasks // and shutdown the ExecutorService @@ -302,7 +328,7 @@ public void close() throws IOException { * An exception representing any error which occurred during the operation. */ @DoesServiceRequest - private void commit() throws StorageException { + private synchronized void commit() throws StorageException { if (this.options.getStoreBlobContentMD5()) { this.parentBlobRef.getProperties().setContentMD5(Base64.encode(this.md5Digest.digest())); } @@ -312,11 +338,11 @@ private void commit() throws StorageException { final CloudBlockBlob blobRef = (CloudBlockBlob) this.parentBlobRef; blobRef.commitBlockList(this.blockList, this.accessCondition, this.options, this.opContext); } - else if (this.streamType == BlobType.PAGE_BLOB) { + else if (this.options.getStoreBlobContentMD5()) { this.parentBlobRef.uploadProperties(this.accessCondition, this.options, this.opContext); } } - + /** * Dispatches a write operation for a given length. * @@ -329,86 +355,140 @@ else if (this.streamType == BlobType.PAGE_BLOB) { * closed. */ @DoesServiceRequest - private synchronized void dispatchWrite(final int writeLength) throws IOException { + private synchronized void dispatchWrite() throws IOException { + final int writeLength = this.outBuffer.size(); if (writeLength == 0) { return; } + + if (this.streamType == BlobType.PAGE_BLOB && (writeLength % Constants.PAGE_SIZE != 0)) { + throw new IOException(String.format(SR.INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER, writeLength)); + } Callable worker = null; - if (this.outstandingRequests > this.options.getConcurrentRequestCount() * 2) { + if (this.threadExecutor.getQueue().size() >= this.options.getConcurrentRequestCount() * 2) { this.waitForTaskToComplete(); + } + + if (this.futureSet.size() >= this.options.getConcurrentRequestCount() * 2) { + this.clearCompletedFutures(); } final ByteArrayInputStream bufferRef = new ByteArrayInputStream(this.outBuffer.toByteArray()); if (this.streamType == BlobType.BLOCK_BLOB) { - final CloudBlockBlob blobRef = (CloudBlockBlob) this.parentBlobRef; - final String blockID = Base64.encode(Utility.getBytesFromLong(this.blockIdSequenceNumber++)); + final String blockID = this.getCurrentBlockId(); + this.blockList.add(new BlockEntry(blockID, BlockSearchMode.LATEST)); worker = new Callable() { @Override public Void call() { - try { - blobRef.uploadBlock(blockID, bufferRef, writeLength, BlobOutputStream.this.accessCondition, - BlobOutputStream.this.options, BlobOutputStream.this.opContext); - } - catch (final IOException e) { - synchronized (BlobOutputStream.this.lastErrorLock) { - BlobOutputStream.this.streamFaulted = true; - BlobOutputStream.this.lastError = e; - } - } - catch (final StorageException e) { - synchronized (BlobOutputStream.this.lastErrorLock) { - BlobOutputStream.this.streamFaulted = true; - BlobOutputStream.this.lastError = Utility.initIOException(e); - } - } + BlobOutputStream.this.writeBlock(bufferRef, blockID, writeLength); return null; } }; } else if (this.streamType == BlobType.PAGE_BLOB) { - final CloudPageBlob blobRef = (CloudPageBlob) this.parentBlobRef; - long tempOffset = this.currentPageOffset; - long tempLength = writeLength; - - final long opWriteLength = tempLength; - final long opOffset = tempOffset; - this.currentPageOffset += writeLength; + final long opOffset = this.currentBlobOffset; + this.currentBlobOffset += writeLength; worker = new Callable() { @Override public Void call() { - try { - blobRef.uploadPages(bufferRef, opOffset, opWriteLength, BlobOutputStream.this.accessCondition, - BlobOutputStream.this.options, BlobOutputStream.this.opContext); - } - catch (final IOException e) { - synchronized (BlobOutputStream.this.lastErrorLock) { - BlobOutputStream.this.streamFaulted = true; - BlobOutputStream.this.lastError = e; - } - } - catch (final StorageException e) { - synchronized (BlobOutputStream.this.lastErrorLock) { - BlobOutputStream.this.streamFaulted = true; - BlobOutputStream.this.lastError = Utility.initIOException(e); - } - } + BlobOutputStream.this.writePages(bufferRef, opOffset, writeLength); + return null; + } + }; + } + else if (this.streamType == BlobType.APPEND_BLOB) { + final long opOffset = this.currentBlobOffset; + this.currentBlobOffset += writeLength; + + // We cannot differentiate between max size condition failing only in the retry versus failing in the + // first attempt and retry even for a single writer scenario. So we will eliminate the latter and handle + // the former in the append block method. + if (this.accessCondition.getIfMaxSizeLessThanOrEqual() != null + && this.currentBlobOffset > this.accessCondition.getIfMaxSizeLessThanOrEqual()) { + this.lastError = new IOException(SR.INVALID_BLOCK_SIZE); + throw this.lastError; + } + + worker = new Callable() { + @Override + public Void call() { + BlobOutputStream.this.appendBlock(bufferRef, opOffset, writeLength); return null; } }; } - // Do work and reset buffer. - this.completionService.submit(worker); - this.outstandingRequests++; - this.currentBufferedBytes = 0; + // Add future to set + this.futureSet.add(this.completionService.submit(worker)); + + // Reset buffer. this.outBuffer = new ByteArrayOutputStream(); } + + private void writeBlock(ByteArrayInputStream blockData, String blockId, long writeLength) { + final CloudBlockBlob blobRef = (CloudBlockBlob) this.parentBlobRef; + + try { + blobRef.uploadBlock(blockId, blockData, writeLength, this.accessCondition, this.options, this.opContext); + } + catch (final IOException e) { + this.lastError = e; + } + catch (final StorageException e) { + this.lastError = Utility.initIOException(e); + } + } + + private void writePages(ByteArrayInputStream pageData, long offset, long writeLength) { + final CloudPageBlob blobRef = (CloudPageBlob) this.parentBlobRef; + + try { + blobRef.uploadPages(pageData, offset, writeLength, this.accessCondition, this.options, this.opContext); + } + catch (final IOException e) { + this.lastError = e; + } + catch (final StorageException e) { + this.lastError = Utility.initIOException(e); + } + } + + private void appendBlock(ByteArrayInputStream blockData, long offset, long writeLength) { + final CloudAppendBlob blobRef = (CloudAppendBlob) this.parentBlobRef; + this.accessCondition.setIfAppendPositionEqual(offset); + + int previousResultsCount = this.opContext.getRequestResults().size(); + try { + blobRef.appendBlock(blockData, writeLength, this.accessCondition, this.options, this.opContext); + } + catch (final IOException e) { + this.lastError = e; + } + catch (final StorageException e) { + if (this.options.getAbsorbConditionalErrorsOnRetry() + && e.getHttpStatusCode() == HttpURLConnection.HTTP_PRECON_FAILED + && e.getExtendedErrorInformation() != null + && e.getExtendedErrorInformation().getErrorCode() != null + && (e.getExtendedErrorInformation().getErrorCode() + .equals(StorageErrorCodeStrings.INVALID_APPEND_POSITION) || e.getExtendedErrorInformation() + .getErrorCode().equals(StorageErrorCodeStrings.INVALID_MAX_BLOB_SIZE_CONDITION)) + && (this.opContext.getRequestResults().size() - previousResultsCount > 1)) { + + // Pre-condition failure on a retry should be ignored in a single writer scenario since + // the request succeeded in the first attempt. + Logger.info(this.opContext, SR.PRECONDITION_FAILURE_IGNORED); + } + else { + this.lastError = Utility.initIOException(e); + } + } + } /** * Flushes this output stream and forces any buffered output bytes to be written out. If any data remains in the @@ -419,57 +499,89 @@ public Void call() { */ @Override @DoesServiceRequest - public synchronized void flush() throws IOException { + public void flush() throws IOException { this.checkStreamState(); - if (this.streamType == BlobType.PAGE_BLOB && this.currentBufferedBytes > 0 - && (this.currentBufferedBytes % Constants.PAGE_SIZE != 0)) { - throw new IOException(String.format(SR.INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER, this.currentBufferedBytes)); - - // Non 512 byte remainder, uncomment to pad with bytes and commit. - /* - * byte[] nullBuff = new byte[BlobConstants.PageSize - this.currentBufferedBytes % BlobConstants.PageSize]; - * this.write(nullBuff); - */ - } - - // Dispatch a write for the current bytes in the buffer - this.dispatchWrite(this.currentBufferedBytes); + this.dispatchWrite(); // Waits for all submitted tasks to complete - while (this.outstandingRequests > 0) { - // Wait for a task to complete - this.waitForTaskToComplete(); + Set> requests = new HashSet>(this.futureSet); + for (Future request : requests) { + // wait for the future to complete + try { + request.get(); + } + catch (Exception e) { + throw Utility.initIOException(e); + } // If that task threw an error, fail fast this.checkStreamState(); } } + + /** + * Generates a new block ID to be used for PutBlock. + * + * @return Base64 encoded block ID + * @throws IOException + */ + private String getCurrentBlockId() throws IOException + { + String blockIdSuffix = String.format("%06d", this.blockList.size()); + + byte[] blockIdInBytes; + try { + blockIdInBytes = (this.blockIdPrefix + blockIdSuffix).getBytes(Constants.UTF8_CHARSET); + } catch (UnsupportedEncodingException e) { + // this should never happen, UTF8 is a default charset + throw new IOException(e); + } + + return Base64.encode(blockIdInBytes); + } /** - * Waits for one task to complete + * Waits for at least one task to complete. * * @throws IOException * If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been * closed. */ private void waitForTaskToComplete() throws IOException { - try { - final Future future = this.completionService.take(); - future.get(); + boolean completed = false; + while (this.completionService.poll() != null) { + completed = true; } - catch (final InterruptedException e) { - throw Utility.initIOException(e); + + if (!completed) { + try { + this.completionService.take(); + } + catch (final InterruptedException e) { + throw Utility.initIOException(e); + } } - catch (final ExecutionException e) { - throw Utility.initIOException(e); + } + + + /** + * Removes futures which are done from the future set. + */ + private void clearCompletedFutures() { + for (Future request : this.futureSet) { + if (request.isDone()) { + this.futureSet.remove(request); + } } - - this.outstandingRequests--; } /** * Writes b.length bytes from the specified byte array to this output stream. + *

+ * If you are using {@link CloudAppendBlob} and are certain of a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag to + * true is acceptable for you. * * @param data * A byte array which represents the data to write. @@ -486,6 +598,10 @@ public void write(final byte[] data) throws IOException { /** * Writes length bytes from the specified byte array starting at offset to this output stream. + *

+ * If you are using {@link CloudAppendBlob} and are certain of a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag to + * true is acceptable for you. * * @param data * A byte array which represents the data to write. @@ -510,6 +626,10 @@ public void write(final byte[] data, final int offset, final int length) throws /** * Writes all data from the InputStream to the Blob. + *

+ * If you are using {@link CloudAppendBlob} and are certain of a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag to + * true is acceptable for you. * * @param sourceStream * An {@link InputStream} object which species the data to write to the Blob. @@ -529,6 +649,10 @@ public void write(final InputStream sourceStream, final long writeLength) throws * Writes the specified byte to this output stream. The general contract for write is that one byte is written to * the output stream. The byte to be written is the eight low-order bits of the argument b. The 24 high-order bits * of b are ignored. + *

+ * If you are using {@link CloudAppendBlob} and are certain of a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag to + * true is acceptable for you. * * @param byteVal * An int which represents the bye value to write. @@ -542,7 +666,7 @@ public void write(final InputStream sourceStream, final long writeLength) throws public void write(final int byteVal) throws IOException { this.write(new byte[] { (byte) (byteVal & 0xFF) }); } - + /** * Writes the data to the buffer and triggers writes to the service as needed. * @@ -562,8 +686,8 @@ public void write(final int byteVal) throws IOException { private synchronized void writeInternal(final byte[] data, int offset, int length) throws IOException { while (length > 0) { this.checkStreamState(); - - final int availableBufferBytes = this.internalWriteThreshold - this.currentBufferedBytes; + + final int availableBufferBytes = this.internalWriteThreshold - this.outBuffer.size(); final int nextWrite = Math.min(availableBufferBytes, length); // If we need to set MD5 then update the digest accordingly @@ -572,12 +696,11 @@ private synchronized void writeInternal(final byte[] data, int offset, int lengt } this.outBuffer.write(data, offset, nextWrite); - this.currentBufferedBytes += nextWrite; offset += nextWrite; length -= nextWrite; - if (this.currentBufferedBytes == this.internalWriteThreshold) { - this.dispatchWrite(this.internalWriteThreshold); + if (this.outBuffer.size() == this.internalWriteThreshold) { + this.dispatchWrite(); } } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java index a80e561b3a6a3..c6c12da8fd73f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java @@ -23,6 +23,11 @@ */ public final class BlobProperties { + /** + * Represents the number of committed blocks on the append blob. + */ + private Integer appendBlobCommittedBlockCount; + /** * Represents the type of the blob. */ @@ -132,6 +137,7 @@ public BlobProperties(final BlobProperties other) { this.contentMD5 = other.contentMD5; this.cacheControl = other.cacheControl; this.pageBlobSequenceNumber = other.pageBlobSequenceNumber; + this.appendBlobCommittedBlockCount = other.appendBlobCommittedBlockCount; } /** @@ -144,6 +150,15 @@ public BlobProperties(final BlobType type) { this.blobType = type; } + /** + * If the blob is an append blob, gets the number of committed blocks. + * + * @return A Integer value that represents the number of committed blocks. + */ + public Integer getAppendBlobCommittedBlockCount() { + return this.appendBlobCommittedBlockCount; + } + /** * Gets the blob type for the blob. * @@ -225,8 +240,9 @@ public CopyState getCopyState() { * The ETag value is a unique identifier that is updated when a write operation is performed against the container. * It may be used to perform operations conditionally, providing concurrency control and improved efficiency. *

- * The {@link AccessCondition#ifMatch} and {@link AccessCondition#ifNoneMatch} methods take an ETag value and return - * an {@link AccessCondition} object that may be specified on the request. + * The {@link AccessCondition#generateIfMatchCondition(String)} and + * {@link AccessCondition#generateIfNoneMatchCondition(String)} methods take an ETag value and return an + * {@link AccessCondition} object that may be specified on the request. * * @return A String which represents the ETag value. */ @@ -278,7 +294,7 @@ public LeaseDuration getLeaseDuration() { public long getLength() { return this.length; } - + /** * If the blob is a page blob, gets the page blob's current sequence number. * @@ -348,6 +364,16 @@ public void setContentType(final String contentType) { this.contentType = contentType; } + /** + * If the blob is an append blob, sets the number of committed blocks. + * + * @param appendBlobCommittedBlockCount + * A Integer value that represents the number of committed blocks. + */ + protected void setAppendBlobCommittedBlockCount(final Integer appendBlobCommittedBlockCount) { + this.appendBlobCommittedBlockCount = appendBlobCommittedBlockCount; + } + /** * Sets the blob type. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java index 6adeb924b6502..7ead9e0aff441 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java @@ -36,6 +36,8 @@ */ final class BlobRequest { + private static final String APPEND_BLOCK_QUERY_ELEMENT_NAME = "appendblock"; + private static final String BLOCK_QUERY_ELEMENT_NAME = "block"; private static final String BLOCK_ID_QUERY_ELEMENT_NAME = "blockid"; @@ -150,6 +152,49 @@ private static void addSnapshot(final UriQueryBuilder builder, final String snap builder.add(Constants.QueryConstants.SNAPSHOT, snapshotVersion); } } + + /** + * Constructs a web request to commit a block to an append blob. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param blobOptions + * A {@link BlobRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudBlobClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + */ + public static HttpURLConnection appendBlock(final URI uri, final BlobRequestOptions blobOptions, + final OperationContext opContext, final AccessCondition accessCondition) + throws StorageException, IOException, URISyntaxException + { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, APPEND_BLOCK_QUERY_ELEMENT_NAME); + + final HttpURLConnection request = createURLConnection(uri, builder, blobOptions, opContext); + + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + accessCondition.applyAppendConditionToRequest(request); + } + + return request; + } /** * Creates a request to copy a blob, Sign with 0 length. @@ -701,14 +746,26 @@ private static HttpURLConnection lease(final URI uri, final BlobRequestOptions b request.setFixedLengthStreamingMode(0); request.setRequestProperty(HeaderConstants.LEASE_ACTION_HEADER, action.toString()); - request.setRequestProperty(HeaderConstants.LEASE_DURATION, leaseTimeInSeconds == null ? "-1" - : leaseTimeInSeconds.toString()); + // Lease duration should only be sent for acquire. + if (action == LeaseAction.ACQUIRE) { + // Assert lease duration is in bounds + if (leaseTimeInSeconds != null && leaseTimeInSeconds != -1) { + Utility.assertInBounds("leaseTimeInSeconds", leaseTimeInSeconds, Constants.LEASE_DURATION_MIN, + Constants.LEASE_DURATION_MAX); + } + + request.setRequestProperty(HeaderConstants.LEASE_DURATION, leaseTimeInSeconds == null ? "-1" + : leaseTimeInSeconds.toString()); + } if (proposedLeaseId != null) { request.setRequestProperty(HeaderConstants.PROPOSED_LEASE_ID_HEADER, proposedLeaseId); } if (breakPeriodInSeconds != null) { + // Assert lease break period is in bounds + Utility.assertInBounds("breakPeriodInSeconds", breakPeriodInSeconds, Constants.LEASE_BREAK_PERIOD_MIN, + Constants.LEASE_BREAK_PERIOD_MAX); request.setRequestProperty(HeaderConstants.LEASE_BREAK_PERIOD_HEADER, breakPeriodInSeconds.toString()); } @@ -1013,9 +1070,14 @@ public static HttpURLConnection putBlob(final URI uri, final BlobRequestOptions properties.setLength(pageBlobSize); } - else { + else if (blobType == BlobType.BLOCK_BLOB){ request.setRequestProperty(BlobConstants.BLOB_TYPE_HEADER, BlobConstants.BLOCK_BLOB); } + else if (blobType == BlobType.APPEND_BLOB){ + request.setFixedLengthStreamingMode(0); + request.setRequestProperty(BlobConstants.BLOB_TYPE_HEADER, BlobConstants.APPEND_BLOB); + request.setRequestProperty(Constants.HeaderConstants.CONTENT_LENGTH, "0"); + } if (accessCondition != null) { accessCondition.applyConditionToRequest(request); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java index 61205a9c0ecb4..d1a2db160211c 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java @@ -26,9 +26,14 @@ public final class BlobRequestOptions extends RequestOptions { /** - * Represents the concurrent number of simultaneous requests per operation. If it's null, it will be set to the - * value specified by the cloud blob client's default request options - * {@link CloudBlobClient#getDefaultRequestOptions} during upload operations. + * Indicates whether a conditional failure should be absorbed on a retry attempt for the request. This option + * is only used by {@link CloudAppendBlob} in upload and openWrite methods. By default, it is set to + * false. Set this to true only for single writer scenario. + */ + private Boolean absorbConditionalErrorsOnRetry = null; + + /** + * Represents the concurrent number of simultaneous requests per operation. The default value is 1. */ private Integer concurrentRequestCount = null; @@ -72,6 +77,7 @@ public BlobRequestOptions() { public BlobRequestOptions(final BlobRequestOptions other) { super(other); if (other != null) { + this.setAbsorbConditionalErrorsOnRetry(other.getAbsorbConditionalErrorsOnRetry()); this.setConcurrentRequestCount(other.getConcurrentRequestCount()); this.setUseTransactionalContentMD5(other.getUseTransactionalContentMD5()); this.setStoreBlobContentMD5(other.getStoreBlobContentMD5()); @@ -94,9 +100,9 @@ public BlobRequestOptions(final BlobRequestOptions other) { * {@link #concurrentRequestCount} field's value is null, it will be set to the value specified by the * cloud blob client's {@link CloudBlobClient#getConcurrentRequestCount} method. */ - protected static final BlobRequestOptions applyDefaults(final BlobRequestOptions options, final BlobType blobType, - final CloudBlobClient client) { - return BlobRequestOptions.applyDefaults(options, blobType, client, true); + protected static final BlobRequestOptions populateAndApplyDefaults(final BlobRequestOptions options, + final BlobType blobType, final CloudBlobClient client) { + return BlobRequestOptions.populateAndApplyDefaults(options, blobType, client, true); } /** @@ -115,20 +121,34 @@ protected static final BlobRequestOptions applyDefaults(final BlobRequestOptions * @param setStartTime * whether to initialize the startTimeInMs field, or not */ - protected static final BlobRequestOptions applyDefaults(final BlobRequestOptions options, final BlobType blobType, - final CloudBlobClient client, final boolean setStartTime) { + protected static final BlobRequestOptions populateAndApplyDefaults(final BlobRequestOptions options, + final BlobType blobType, final CloudBlobClient client, final boolean setStartTime) { BlobRequestOptions modifiedOptions = new BlobRequestOptions(options); BlobRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), setStartTime); - return BlobRequestOptions.applyDefaultsInternal(modifiedOptions, blobType, client); + BlobRequestOptions.applyDefaults(modifiedOptions, blobType); + return modifiedOptions; } - private static final BlobRequestOptions applyDefaultsInternal(final BlobRequestOptions modifiedOptions, - final BlobType blobtype, final CloudBlobClient client) { + /** + * Applies defaults to the options passed in. + * + * @param modifiedOptions + * The options to apply defaults to. + */ + protected static void applyDefaults(final BlobRequestOptions modifiedOptions, final BlobType blobtype) { Utility.assertNotNull("modifiedOptions", modifiedOptions); RequestOptions.applyBaseDefaultsInternal(modifiedOptions); - if (modifiedOptions.getConcurrentRequestCount() == null) { - modifiedOptions.setConcurrentRequestCount(BlobConstants.DEFAULT_CONCURRENT_REQUEST_COUNT); + + if (modifiedOptions.getAbsorbConditionalErrorsOnRetry() == null) { + modifiedOptions.setAbsorbConditionalErrorsOnRetry(false); } + + if (blobtype == BlobType.APPEND_BLOB) { + // Append blobs must be done in serial. + modifiedOptions.setConcurrentRequestCount(1); + } else if (modifiedOptions.getConcurrentRequestCount() == null) { + modifiedOptions.setConcurrentRequestCount(BlobConstants.DEFAULT_CONCURRENT_REQUEST_COUNT); + } if (modifiedOptions.getSingleBlobPutThresholdInBytes() == null) { modifiedOptions.setSingleBlobPutThresholdInBytes(BlobConstants.DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES); @@ -139,22 +159,27 @@ private static final BlobRequestOptions applyDefaultsInternal(final BlobRequestO } if (modifiedOptions.getStoreBlobContentMD5() == null) { - modifiedOptions.setStoreBlobContentMD5(blobtype == BlobType.BLOCK_BLOB); + if (blobtype != BlobType.UNSPECIFIED) { + modifiedOptions.setStoreBlobContentMD5(blobtype == BlobType.BLOCK_BLOB); + } } if (modifiedOptions.getDisableContentMD5Validation() == null) { modifiedOptions.setDisableContentMD5Validation(false); } - - return modifiedOptions; } /** * Populates any null fields in the first requestOptions object with values from the second requestOptions object. */ - private static final BlobRequestOptions populateRequestOptions(BlobRequestOptions modifiedOptions, + private static void populateRequestOptions(BlobRequestOptions modifiedOptions, final BlobRequestOptions clientOptions, final boolean setStartTime) { RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, setStartTime); + + if (modifiedOptions.getAbsorbConditionalErrorsOnRetry() == null) { + modifiedOptions.setAbsorbConditionalErrorsOnRetry(clientOptions.getAbsorbConditionalErrorsOnRetry()); + } + if (modifiedOptions.getConcurrentRequestCount() == null) { modifiedOptions.setConcurrentRequestCount(clientOptions.getConcurrentRequestCount()); } @@ -174,10 +199,18 @@ private static final BlobRequestOptions populateRequestOptions(BlobRequestOption if (modifiedOptions.getDisableContentMD5Validation() == null) { modifiedOptions.setDisableContentMD5Validation(clientOptions.getDisableContentMD5Validation()); } - - return modifiedOptions; } + /** + * Indicates whether a conditional failure should be absorbed on a retry attempt for the request. For more + * information about absorb conditinal errors on retry defaults, see {@link #setAbsorbConditionalErrorsOnRetry(Boolean)}. + * + * @return the absorbConditionalErrorsOnRetry + */ + public Boolean getAbsorbConditionalErrorsOnRetry() { + return this.absorbConditionalErrorsOnRetry; + } + /** * Gets the concurrent number of simultaneous requests per operation. For more information about concurrent request * count defaults, see {@link #setConcurrentRequestCount(Integer)}. @@ -232,12 +265,28 @@ public Integer getSingleBlobPutThresholdInBytes() { return this.singleBlobPutThresholdInBytes; } + /** + * Sets whether a conditional failure should be absorbed on a retry attempt for the request. This option + * is only used by {@link CloudAppendBlob} in upload and openWrite methods. By default, it is set to + * false. Set this to true only for single writer scenario. + *

+ * You can change the absorbConditionalErrorsOnRetry value on this request by setting this property. You can also + * change the value on the {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests + * made via the service client will use that absorbConditionalErrorsOnRetry value. + * + * @param absorbConditionalErrorsOnRetry + * the absorbConditionalErrorsOnRetry to set + */ + public void setAbsorbConditionalErrorsOnRetry(final Boolean absorbConditionalErrorsOnRetry) { + this.absorbConditionalErrorsOnRetry = absorbConditionalErrorsOnRetry; + } + /** * Sets the concurrent number of simultaneous requests per operation. *

* The default concurrent request count is set in the client and is by default 1, indicating no concurrency. You can * change the concurrent request count on this request by setting this property. You can also change the value on - * the {@link BlobServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the + * the {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests made via the * service client will use that concurrent request count. * * @param concurrentRequestCount @@ -253,7 +302,7 @@ public void setConcurrentRequestCount(final Integer concurrentRequestCount) { *

* The default useTransactionalContentMD5 value is set in the client and is by default false. You can * change the useTransactionalContentMD5 value on this request by setting this property. You can also change the - * value on the {@link BlobServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via + * value on the {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests made via * the service client will use that useTransactionalContentMD5 value. * * @param useTransactionalContentMD5 @@ -269,7 +318,7 @@ public void setUseTransactionalContentMD5(final Boolean useTransactionalContentM *

* The default storeBlobContentMD5 value is set in the client and is by default true for block blobs. * You can change the storeBlobContentMD5 value on this request by setting this property. You can also change the - * value on the {@link BlobServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via + * value on the {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests made via * the service client will use that storeBlobContentMD5 value. * * @param storeBlobContentMD5 @@ -284,7 +333,7 @@ public void setStoreBlobContentMD5(final Boolean storeBlobContentMD5) { *

* The default disableContentMD5Validation value is set in the client and is by default false. You can * change the disableContentMD5Validation value on this request by setting this property. You can also change the - * value on the {@link BlobServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via + * value on the {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests made via * the service client will use that disableContentMD5Validation value. * * @param disableContentMD5Validation @@ -299,7 +348,7 @@ public void setDisableContentMD5Validation(final Boolean disableContentMD5Valida *

* The default threshold size is set in the client and is by default 32MB. You can change the threshold size on this * request by setting this property. You can also change the value on the - * {@link BlobServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service + * {@link CloudBlobClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service * client will use that threshold size. * * @param singleBlobPutThresholdInBytes diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java index 1972af96f16ce..cb3b5514982e0 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java @@ -106,6 +106,13 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { properties.setPageBlobSequenceNumber(Long.parseLong(sequenceNumber)); } + // Get committed block count + final String comittedBlockCount = request.getHeaderField(Constants.HeaderConstants.BLOB_COMMITTED_BLOCK_COUNT); + if (!Utility.isNullOrEmpty(comittedBlockCount)) + { + properties.setAppendBlobCommittedBlockCount(Integer.parseInt(comittedBlockCount)); + } + attributes.setStorageUri(resourceURI); attributes.setSnapshotID(snapshotID); @@ -163,7 +170,8 @@ public static BlobContainerAttributes getBlobContainerAttributes(final HttpURLCo public static CopyState getCopyState(final HttpURLConnection request) throws URISyntaxException, ParseException { String copyStatusString = request.getHeaderField(Constants.HeaderConstants.COPY_STATUS); if (!Utility.isNullOrEmpty(copyStatusString)) { - CopyState copyState = new CopyState(); + final CopyState copyState = new CopyState(); + copyState.setStatus(CopyStatus.parse(copyStatusString)); copyState.setCopyId(request.getHeaderField(Constants.HeaderConstants.COPY_ID)); copyState.setStatusDescription(request.getHeaderField(Constants.HeaderConstants.COPY_STATUS_DESCRIPTION)); @@ -180,12 +188,12 @@ public static CopyState getCopyState(final HttpURLConnection request) throws URI copyState.setSource(new URI(copySourceString)); } - final String copyCompletionTimeString = request - .getHeaderField(Constants.HeaderConstants.COPY_COMPLETION_TIME); + final String copyCompletionTimeString = + request.getHeaderField(Constants.HeaderConstants.COPY_COMPLETION_TIME); if (!Utility.isNullOrEmpty(copyCompletionTimeString)) { copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(copyCompletionTimeString)); } - + return copyState; } else { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobType.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobType.java index 254289efa5ecd..601b64c7229db 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobType.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobType.java @@ -35,7 +35,12 @@ public enum BlobType { /** * Specifies the blob is a page blob. */ - PAGE_BLOB; + PAGE_BLOB, + + /** + * Specifies the blob is an append blob. + */ + APPEND_BLOB; /** * Returns the enum value representing the blob type for the specified string. @@ -55,6 +60,9 @@ else if ("blockblob".equals(typeString.toLowerCase(Locale.US))) { else if ("pageblob".equals(typeString.toLowerCase(Locale.US))) { return PAGE_BLOB; } + else if ("appendblob".equals(typeString.toLowerCase(Locale.US))) { + return APPEND_BLOB; + } else { return UNSPECIFIED; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java new file mode 100644 index 0000000000000..95d3726328765 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java @@ -0,0 +1,995 @@ +/** + * Copyright 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.azure.storage.blob; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; + +import com.microsoft.azure.storage.AccessCondition; +import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.DoesServiceRequest; +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageUri; +import com.microsoft.azure.storage.core.ExecutionEngine; +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.StorageRequest; +import com.microsoft.azure.storage.core.StreamMd5AndLength; +import com.microsoft.azure.storage.core.Utility; + +/** + * Represents a Microsoft Azure Append Blob. + */ +public final class CloudAppendBlob extends CloudBlob { + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute URI and storage service + * client. + * + * @param blobAbsoluteUri + * A java.net.URI object which represents the absolute URI to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final URI blobAbsoluteUri) throws StorageException { + this(new StorageUri(blobAbsoluteUri)); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute URI and storage service + * client. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object which represents the absolute URI to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final StorageUri blobAbsoluteUri) throws StorageException { + this(blobAbsoluteUri, (StorageCredentials)null); + } + + /** + * Creates an instance of the CloudAppendBlob class by copying values from another append blob. + * + * @param otherBlob + * A CloudAppendBlob object which represents the append blob to copy. + */ + public CloudAppendBlob(final CloudAppendBlob otherBlob) { + super(otherBlob); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute URI and credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final URI blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(blobAbsoluteUri), credentials); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute URI, snapshot ID, and + * credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final URI blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + this(new StorageUri(blobAbsoluteUri), snapshotID, credentials); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute StorageUri + * and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final StorageUri blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(blobAbsoluteUri, null /* snapshotID */, credentials); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified absolute StorageUri, snapshot + * ID, and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudAppendBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + super(BlobType.APPEND_BLOB, blobAbsoluteUri, snapshotID, credentials); + } + + /** + * Creates an instance of the CloudAppendBlob class using the specified URI, snapshot ID, and cloud blob + * client. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object which represents the absolute URI to the blob. + * @param snapshotID + * A String which represents the snapshot version, if applicable. + * @param client + * A {@link CloudBlobContainer} object which represents the container to use for the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + protected CloudAppendBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final CloudBlobClient client) + throws StorageException { + super(BlobType.APPEND_BLOB, blobAbsoluteUri, snapshotID, client); + } + + /** + * Requests the service to start copying a append blob's contents, properties, and metadata to a new append blob. + * + * @param sourceBlob + * A CloudAppendBlob object that represents the source blob to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudAppendBlob sourceBlob) throws StorageException, URISyntaxException { + return this.startCopy(sourceBlob, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a append blob's contents, properties, and metadata to a new append blob, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param sourceBlob + * A CloudAppendBlob object that represents the source blob to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudAppendBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceBlob", sourceBlob); + return this.startCopy( + sourceBlob.getQualifiedUri(), sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Creates an empty append blob. If the blob already exists, this will replace it. + *

+ * To avoid overwriting and instead throw an error, please use the + * {@link #createOrReplace(AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate + * {@link AccessCondition}. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void createOrReplace() throws StorageException { + this.createOrReplace(null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Creates an append blob using the specified request options and operation context. If the blob already exists, + * this will replace it. + *

+ * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. + * + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void createOrReplace(final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + assertNoWriteOperationForSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.APPEND_BLOB, this.blobServiceClient); + + ExecutionEngine.executeWithRetry(this.blobServiceClient, this, + this.createImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest createImpl(final AccessCondition accessCondition, + final BlobRequestOptions options) { + final StorageRequest putRequest = new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + throws Exception { + return BlobRequest.putBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition, blob.properties, BlobType.APPEND_BLOB, 0); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) { + BlobRequest.addMetadata(connection, blob.metadata, context); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + blob.getProperties().setLength(0); + return null; + } + + }; + + return putRequest; + } + + /** + * Commits a new block of data to the end of the blob. + * + * @param sourceStream + * An {@link InputStream} object that represents the input stream to write to the append blob. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * + * @return The offset at which the block was appended. + * @throws IOException + * If an I/O exception occurred. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public Long appendBlock(final InputStream sourceStream, final long length) throws IOException, StorageException + { + return this.appendBlock(sourceStream, length, null, null, null); + } + + /** + * Commits a new block of data to the end of the blob. + * + * @param sourceStream + * An {@link InputStream} object that represents the input stream to write to the Append blob. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @return The offset at which the block was appended. + * @throws IOException + * If an I/O exception occurred. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public Long appendBlock(final InputStream sourceStream, final long length, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException + { + if (length < -1) { + throw new IllegalArgumentException(SR.STREAM_LENGTH_NEGATIVE); + } + + assertNoWriteOperationForSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.APPEND_BLOB, this.blobServiceClient); + + if (sourceStream.markSupported()) { + // Mark sourceStream for current position. + sourceStream.mark(Constants.MAX_MARK_LENGTH); + } + + InputStream bufferedStreamReference = sourceStream; + StreamMd5AndLength descriptor = new StreamMd5AndLength(); + descriptor.setLength(length); + + if (!sourceStream.markSupported()) { + // needs buffering + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + descriptor = Utility.writeToOutputStream(sourceStream, byteStream, length, false /* rewindSourceStream */, + options.getUseTransactionalContentMD5(), opContext, options); + + bufferedStreamReference = new ByteArrayInputStream(byteStream.toByteArray()); + } + else if (length < 0 || options.getUseTransactionalContentMD5()) { + // If the stream is of unknown length or we need to calculate the + // MD5, then we we need to read the stream contents first + descriptor = Utility.analyzeStream(sourceStream, length, -1L, true /* rewindSourceStream */, + options.getUseTransactionalContentMD5()); + } + + if (descriptor.getLength() > 4 * Constants.MB) { + throw new IllegalArgumentException(SR.STREAM_LENGTH_GREATER_THAN_4MB); + } + + StorageRequest appendBlockImpl = appendBlockImpl(descriptor.getMd5(), bufferedStreamReference, + descriptor.getLength(), accessCondition, options, opContext); + return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, appendBlockImpl, options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest appendBlockImpl(final String md5, + final InputStream sourceStream, final long length, final AccessCondition accessCondition, + final BlobRequestOptions options, final OperationContext opContext) { + + final StorageRequest putRequest = new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudBlobClient client, CloudAppendBlob blob, OperationContext context) + throws Exception { + this.setSendStream(sourceStream); + this.setLength(length); + return BlobRequest.appendBlock(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()), + options, opContext, accessCondition); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudAppendBlob blob, OperationContext context) { + if (options.getUseTransactionalContentMD5()) { + connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, md5); + } + } + + @Override + public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context); + } + + @Override + public Long preProcessResponse(CloudAppendBlob blob, CloudBlobClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + Long appendOffset = null; + if (this.getConnection().getHeaderField(Constants.HeaderConstants.BLOB_APPEND_OFFSET) != null) + { + appendOffset = Long.parseLong(this.getConnection().getHeaderField(Constants.HeaderConstants.BLOB_APPEND_OFFSET)); + } + + blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + blob.updateCommittedBlockCountFromResponse(this.getConnection()); + + return appendOffset; + } + + @Override + public void recoveryAction(OperationContext context) throws IOException { + sourceStream.reset(); + sourceStream.mark(Constants.MAX_MARK_LENGTH); + } + }; + + return putRequest; + } + + /** + * Updates the blob's committed block count from the web request. + * + * @param request + * The web request from which to parse the committed block count. + */ + private void updateCommittedBlockCountFromResponse(HttpURLConnection request) { + final String comittedBlockCount = request.getHeaderField(Constants.HeaderConstants.BLOB_COMMITTED_BLOCK_COUNT); + if (!Utility.isNullOrEmpty(comittedBlockCount)) + { + this.getProperties().setAppendBlobCommittedBlockCount(Integer.parseInt(comittedBlockCount)); + } + } + + /** + * Appends a stream to an append blob. This API should be used strictly in a single writer scenario because the API + * internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple + * writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param sourceStream + * A {@link InputStream} object providing the blob content to append. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + @DoesServiceRequest + public void append(InputStream sourceStream, final long length) throws StorageException, IOException + { + this.append(sourceStream, length, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /** + * Appends a stream to an append blob. This API should be used strictly in a single writer scenario because the API + * internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple + * writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param sourceStream + * A {@link InputStream} object providing the blob content to append. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + @DoesServiceRequest + public void append(InputStream sourceStream, final long length, AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException + { + assertNoWriteOperationForSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.APPEND_BLOB, this.blobServiceClient); + + if (sourceStream.markSupported()) { + // Mark sourceStream for current position. + sourceStream.mark(Constants.MAX_MARK_LENGTH); + } + + final BlobOutputStream streamRef = this.openWriteExisting(accessCondition, options, opContext); + try { + streamRef.write(sourceStream, length); + } + finally { + streamRef.close(); + } + } + + /** + * Appends the contents of a byte array to an append blob.This API should be used strictly in a single writer + * scenario because the API internally uses the append-offset conditional header to avoid duplicate blocks which + * does not work in a multiple writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param buffer + * A byte array which represents the data to append to the blob. + * @param offset + * A int which represents the offset of the byte array from which to start the data upload. + * @param length + * An int which represents the number of bytes to upload from the input buffer. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendFromByteArray(final byte[] buffer, final int offset, final int length) throws StorageException, + IOException { + appendFromByteArray(buffer, offset, length, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Appends the contents of a byte array to an append blob.This API should be used strictly in a single writer + * scenario because the API internally uses the append-offset conditional header to avoid duplicate blocks which + * does not work in a multiple writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param buffer + * A byte array which represents the data to append to the blob. + * @param offset + * A int which represents the offset of the byte array from which to start the data upload. + * @param length + * An int which represents the number of bytes to upload from the input buffer. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendFromByteArray(final byte[] buffer, final int offset, final int length, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length); + this.append(inputStream, length, accessCondition, options, opContext); + inputStream.close(); + } + + /** + * Appends a file to an append blob. This API should be used strictly in a single writer scenario because the API + * internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple + * writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param path + * A String which represents the path to the file to be appended. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendFromFile(final String path) throws StorageException, IOException { + appendFromFile(path, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Appends a file to an append blob. This API should be used strictly in a single writer scenario because the API + * internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple + * writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param path + * A String which represents the path to the file to be appended. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendFromFile(final String path, final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException, IOException { + File file = new File(path); + long fileLength = file.length(); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + this.append(inputStream, fileLength, accessCondition, options, opContext); + inputStream.close(); + } + + /** + * Appends a string of text to an append blob using the platform's default encoding. This API should be used + * strictly in a single writer scenario because the API internally uses the append-offset conditional header to + * avoid duplicate blocks which does not work in a multiple writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param content + * A String which represents the content that will be appended to the blob. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendText(final String content) throws StorageException, IOException { + this.appendText(content, null /* charsetName */, null /* accessCondition */, null /* options */, + null /* opContext */); + } + + /** + * Appends a string of text to an append blob using the specified encoding. This API should be used strictly in a + * single writer scenario because the API internally uses the append-offset conditional header to avoid duplicate + * blocks which does not work in a multiple writer scenario. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param content + * A String which represents the content that will be appended to the blob. + * @param charsetName + * A String which represents the name of the charset to use to encode the content. + * If null, the platform's default encoding is used. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + * If an I/O exception occurred. + */ + public void appendText(final String content, final String charsetName, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException { + byte[] bytes = (charsetName == null) ? content.getBytes() : content.getBytes(charsetName); + this.appendFromByteArray(bytes, 0, bytes.length, accessCondition, options, opContext); + } + + /** + * Opens an output stream object to write data to the append blob. The append blob must already exist and will be + * appended to. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public BlobOutputStream openWriteExisting() throws StorageException { + return this.openWriteExisting(null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Opens an output stream object to write data to the append blob, using the specified lease ID, request options and + * operation context. The append blob must already exist and will be appended to. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + return this.openOutputStreamInternal(false, accessCondition, options, opContext); + } + + /** + * Opens an output stream object to write data to the append blob. The append blob does not need to yet exist. If + * the blob already exists, this will replace it. + *

+ * To avoid overwriting and instead throw an error, please use the + * {@link #openWriteNew(AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate + * {@link AccessCondition}. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public BlobOutputStream openWriteNew() throws StorageException { + return this.openWriteNew(null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Opens an output stream object to write data to the append blob, using the specified lease ID, request options and + * operation context. The append blob does not need to yet exist. If the blob already exists, this will replace it. + *

+ * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public BlobOutputStream openWriteNew(AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + return this.openOutputStreamInternal(true, accessCondition, options, opContext); + } + + /** + * Opens an output stream object to write data to the append blob, using the specified lease ID, request options and + * operation context. + * + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + private BlobOutputStream openOutputStreamInternal(boolean create, AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + assertNoWriteOperationForSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + BlobRequestOptions modifiedOptions = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.APPEND_BLOB, + this.blobServiceClient, false /* setStartTime */); + + if (create) { + this.createOrReplace(accessCondition, modifiedOptions, opContext); + } else { + if (modifiedOptions.getStoreBlobContentMD5()) { + throw new IllegalArgumentException(SR.APPEND_BLOB_MD5_NOT_POSSIBLE); + } + + // Download attributes to check the etag and date access conditions and + // to get the blob length to verify the append position on the first write. + this.downloadAttributes(accessCondition, modifiedOptions, opContext); + } + + // Use an access condition with the etag and date conditions removed as we will be + // appending to the blob and these properties will change each time we append a block. + AccessCondition appendCondition = new AccessCondition(); + if (accessCondition != null) { + appendCondition.setLeaseID(accessCondition.getLeaseID()); + appendCondition.setIfAppendPositionEqual(accessCondition.getIfAppendPositionEqual()); + appendCondition.setIfMaxSizeLessThanOrEqual(accessCondition.getIfMaxSizeLessThanOrEqual()); + } + + return new BlobOutputStream(this, appendCondition, modifiedOptions, opContext); + } + + /** + * Uploads the source stream data to the append blob. If the blob already exists on the service, it will be + * overwritten. + *

+ * If you want to append data to an already existing blob, please see {@link #appendBlock(InputStream, long)}. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param sourceStream + * An {@link InputStream} object to read from. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * + * @throws IOException + * If an I/O exception occurred. + * @throws StorageException + * If a storage service error occurred. + */ + @Override + @DoesServiceRequest + public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException { + this.upload(sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Uploads the source stream data to the append blob using the specified lease ID, request options, and operation + * context. If the blob already exists on the service, it will be overwritten. + *

+ * If you want to append data to an already existing blob, please see {@link #appendBlock(InputStream, long)}. + *

+ * If you are doing writes in a single writer scenario, please look at + * {@link BlobRequestOptions#setAbsorbConditionalErrorsOnRetry(Boolean)} and see if setting this flag + * to true is acceptable for you. + * + * @param sourceStream + * An {@link InputStream} object to read from. + * @param length + * A long which represents the length, in bytes, of the stream data, or -1 if unknown. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws IOException + * If an I/O exception occurred. + * @throws StorageException + * If a storage service error occurred. + */ + @Override + @DoesServiceRequest + public void upload(final InputStream sourceStream, final long length, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException { + assertNoWriteOperationForSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.APPEND_BLOB, this.blobServiceClient); + + if (sourceStream.markSupported()) { + // Mark sourceStream for current position. + sourceStream.mark(Constants.MAX_MARK_LENGTH); + } + + final BlobOutputStream streamRef = this.openWriteNew(accessCondition, options, opContext); + try { + streamRef.write(sourceStream, length); + } + finally { + streamRef.close(); + } + } + + /** + * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream}. + * + * @param streamWriteSizeInBytes + * An int which represents the maximum block size, in bytes, for writing to an append blob + * while using a {@link BlobOutputStream} object, ranging from 16 KB to 4 MB, inclusive. + * + * @throws IllegalArgumentException + * If streamWriteSizeInBytes is less than 16 KB or greater than 4 MB. + */ + @Override + public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) { + if (streamWriteSizeInBytes > Constants.MAX_BLOCK_SIZE || streamWriteSizeInBytes < 16 * Constants.KB) { + throw new IllegalArgumentException("StreamWriteSizeInBytes"); + } + + this.streamWriteSizeInBytes = streamWriteSizeInBytes; + } +} diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java index dac9d0bc3d801..62e12034be80a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java @@ -40,6 +40,7 @@ import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.SharedAccessPolicy; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; @@ -67,7 +68,7 @@ public abstract class CloudBlob implements ListBlobItem { /** * Holds the metadata for the blob. */ - HashMap metadata; + HashMap metadata = new HashMap(); /** * Holds the properties of the blob. @@ -121,7 +122,6 @@ public abstract class CloudBlob implements ListBlobItem { * A {@link BlobType} value which represents the type of the blob. */ protected CloudBlob(final BlobType type) { - this.metadata = new HashMap(); this.properties = new BlobProperties(type); } @@ -141,17 +141,12 @@ protected CloudBlob(final BlobType type) { protected CloudBlob(final BlobType type, final StorageUri uri, final CloudBlobClient client) throws StorageException { this(type); + this.parseQueryAndVerify(uri, client == null ? null : client.getCredentials()); - Utility.assertNotNull("blobAbsoluteUri", uri); - - this.blobServiceClient = client; - this.storageUri = uri; - - this.parseURIQueryStringAndVerify( - this.storageUri, - client, - client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) : client - .isUsePathStyleUris()); + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.blobServiceClient = client; + } } /** @@ -175,7 +170,7 @@ protected CloudBlob(final BlobType type, final StorageUri uri, final CloudBlobCl this(type, uri, client); this.container = container; } - + /** * Creates an instance of the CloudBlob class using the specified URI, snapshot ID, and cloud blob * client. @@ -187,7 +182,7 @@ protected CloudBlob(final BlobType type, final StorageUri uri, final CloudBlobCl * @param snapshotID * A String that represents the snapshot version, if applicable. * @param client - * A {@link CloudBlobContainer} object that represents the container to use for the blob. + * A {@link CloudBlobClient} object that specifies the endpoint for the Blob service. * * @throws StorageException * If a storage service error occurred. @@ -204,6 +199,54 @@ protected CloudBlob(final BlobType type, final StorageUri uri, final String snap } } } + + /** + * Creates an instance of the CloudBlob class using the specified URI and cloud blob client. + * + * @param type + * A {@link BlobType} value which represents the type of the blob. + * @param uri + * A {@link StorageUri} object that represents the URI to the blob, beginning with the container name. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + protected CloudBlob(final BlobType type, final StorageUri uri, final StorageCredentials credentials) + throws StorageException { + this(type, uri, null /* snapshotID */, credentials); + } + + /** + * Creates an instance of the CloudBlob class using the specified URI, snapshot ID, and cloud blob + * client. + * + * @param type + * A {@link BlobType} value which represents the type of the blob. + * @param uri + * A {@link StorageUri} object that represents the URI to the blob, beginning with the container name. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + protected CloudBlob(final BlobType type, final StorageUri uri, final String snapshotID, + final StorageCredentials credentials) throws StorageException { + this(type); + this.parseQueryAndVerify(uri, credentials); + + if (snapshotID != null) { + if (this.snapshotID != null) { + throw new IllegalArgumentException(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED); + } + else { + this.snapshotID = snapshotID; + } + } + } /** * Creates an instance of the CloudBlob class by copying values from another blob. @@ -212,7 +255,6 @@ protected CloudBlob(final BlobType type, final StorageUri uri, final String snap * A CloudBlob object that represents the blob to copy. */ protected CloudBlob(final CloudBlob otherBlob) { - this.metadata = new HashMap(); this.properties = new BlobProperties(otherBlob.properties); if (otherBlob.metadata != null) { @@ -274,7 +316,7 @@ public final void abortCopy(final String copyId, final AccessCondition accessCon } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.abortCopyImpl(copyId, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -297,7 +339,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context); } @Override @@ -315,6 +357,19 @@ public Void preProcessResponse(CloudBlob parentObject, CloudBlobClient client, O return putRequest; } + /** + * Acquires a new infinite lease on the blob. + * + * @return A String that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease() throws StorageException { + return this.acquireLease(null /* leaseTimeInSeconds */, null /* proposedLeaseId */); + } + /** * Acquires a new lease on the blob with the specified lease time and proposed lease ID. * @@ -379,7 +434,7 @@ public final String acquireLease(final Integer leaseTimeInSeconds, final String } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.acquireLeaseImpl(leaseTimeInSeconds, proposedLeaseId, accessCondition, options), @@ -402,7 +457,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -434,12 +489,16 @@ protected final void assertCorrectBlobType() throws StorageException { throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE, BlobType.BLOCK_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306, null, null); - } - if (this instanceof CloudPageBlob && this.properties.getBlobType() != BlobType.PAGE_BLOB) { + } + else if (this instanceof CloudPageBlob && this.properties.getBlobType() != BlobType.PAGE_BLOB) { throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE, BlobType.PAGE_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306, null, null); - + } + else if (this instanceof CloudAppendBlob && this.properties.getBlobType() != BlobType.APPEND_BLOB) { + throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE, + BlobType.APPEND_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306, + null, null); } } @@ -508,7 +567,7 @@ public final long breakLease(final Integer breakPeriodInSeconds, final AccessCon } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.breakLeaseImpl(breakPeriodInSeconds, accessCondition, options), options.getRetryPolicyFactory(), @@ -531,7 +590,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -611,7 +670,7 @@ public final String changeLease(final String proposedLeaseId, final AccessCondit } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.changeLeaseImpl(proposedLeaseId, accessCondition, options), options.getRetryPolicyFactory(), @@ -633,7 +692,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -664,7 +723,10 @@ public String preProcessResponse(CloudBlob blob, CloudBlobClient client, Operati * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * + * @deprecated as of 3.0.0. Use {@link CloudBlob#startCopy(URI)} instead. */ + @Deprecated @DoesServiceRequest public final String startCopyFromBlob(final CloudBlob sourceBlob) throws StorageException, URISyntaxException { return this.startCopyFromBlob(sourceBlob, null /* sourceAccessCondition */, @@ -695,83 +757,144 @@ public final String startCopyFromBlob(final CloudBlob sourceBlob) throws Storage * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException - * + * + * @deprecated as of 3.0.0. Use {@link CloudBlob#startCopy( + * URI, AccessCondition, AccessCondition, BlobRequestOptions, OperationContext)} instead. */ + @Deprecated @DoesServiceRequest public final String startCopyFromBlob(final CloudBlob sourceBlob, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { Utility.assertNotNull("sourceBlob", sourceBlob); - return this.startCopyFromBlob( + return this.startCopy( sourceBlob.getServiceClient().getCredentials().transformUri(sourceBlob.getQualifiedUri()), sourceAccessCondition, destinationAccessCondition, options, opContext); - } /** - * Requests the service to start copying a blob's contents, properties, and metadata to a new blob. + * Requests the service to start copying a URI's contents, properties, and metadata to a new blob. * * @param source - * A java.net.URI The URI of a source blob. + * A java.net.URI The source URI. URIs for resources outside of Azure + * may only be copied into block blobs. * * @return A String which represents the copy ID associated with the copy operation. * * @throws StorageException - * If a storage service error occurred. + * If a storage service error occurred. + * + * @deprecated as of 3.0.0. Use {@link CloudBlob#startCopy(URI)} instead. */ + @Deprecated @DoesServiceRequest public final String startCopyFromBlob(final URI source) throws StorageException { - return this.startCopyFromBlob(source, null /* sourceAccessCondition */, - null /* destinationAccessCondition */, null /* options */, null /* opContext */); + return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */, + null /* options */, null /* opContext */); } /** - * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the + * Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the * specified access conditions, lease ID, request options, and operation context. * * @param source - * A java.net.URI The URI of a source blob. + * A java.net.URI The source URI. URIs for resources outside of Azure + * may only be copied into block blobs. * @param sourceAccessCondition - * An {@link AccessCondition} object that represents the access conditions for the source blob. + * An {@link AccessCondition} object that represents the access conditions for the source. * @param destinationAccessCondition - * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * An {@link AccessCondition} object that represents the access conditions for the destination. * @param options - * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudBlobClient}). + * A {@link BlobRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudBlobClient}). * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. * * @return A String which represents the copy ID associated with the copy operation. * * @throws StorageException - * If a storage service error occurred. - * + * If a storage service error occurred. + * + * @deprecated as of 3.0.0. Use {@link CloudBlob#startCopy( + * URI, AccessCondition, AccessCondition, BlobRequestOptions, OperationContext)} instead. */ + @Deprecated @DoesServiceRequest public final String startCopyFromBlob(final URI source, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { + return this.startCopy(source, sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Requests the service to start copying a URI's contents, properties, and metadata to a new blob. + * + * @param source + * A java.net.URI The source URI. URIs for resources outside of Azure + * may only be copied into block blobs. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String startCopy(final URI source) throws StorageException { + return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */, + null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the + * specified access conditions, lease ID, request options, and operation context. + * + * @param source + * A java.net.URI The source URI. URIs for resources outside of Azure + * may only be copied into block blobs. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final String startCopy(final URI source, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - this.startCopyFromBlobImpl(source, sourceAccessCondition, destinationAccessCondition, options), + this.startCopyImpl(source, sourceAccessCondition, destinationAccessCondition, options), options.getRetryPolicyFactory(), opContext); } - private StorageRequest startCopyFromBlobImpl(final URI source, - final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, - final BlobRequestOptions options) { + private StorageRequest startCopyImpl( + final URI source, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, final BlobRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) @@ -790,7 +913,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context); } @Override @@ -882,7 +1005,7 @@ public final CloudBlob createSnapshot(final HashMap metadata, } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine .executeWithRetry(this.blobServiceClient, this, @@ -913,9 +1036,10 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } + @SuppressWarnings("deprecation") @Override public CloudBlob preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) throws Exception { @@ -931,6 +1055,9 @@ public CloudBlob preProcessResponse(CloudBlob blob, CloudBlobClient client, Oper else if (blob instanceof CloudPageBlob) { snapshot = new CloudPageBlob(blob.getStorageUri(), snapshotTime, client); } + else if (blob instanceof CloudAppendBlob) { + snapshot = new CloudAppendBlob(blob.getStorageUri(), snapshotTime, client); + } snapshot.setProperties(blob.properties); // use the specified metadata if not null : otherwise blob's metadata @@ -991,7 +1118,7 @@ public final void delete(final DeleteSnapshotsOption deleteSnapshotsOption, fina } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.deleteImpl(deleteSnapshotsOption, accessCondition, options), options.getRetryPolicyFactory(), @@ -1049,7 +1176,7 @@ public final boolean deleteIfExists() throws StorageException { public final boolean deleteIfExists(final DeleteSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); boolean exists = this.exists(true, accessCondition, options, opContext); if (exists) { @@ -1089,7 +1216,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1146,7 +1273,7 @@ public final void download(final OutputStream outStream, final AccessCondition a } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToStreamImpl( null /* blobOffset */, null /* length */, outStream, accessCondition, options, opContext), options @@ -1206,7 +1333,7 @@ public final void downloadRange(final long offset, final Long length, final Outp } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) { throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER); @@ -1260,7 +1387,7 @@ public final void downloadAttributes(final AccessCondition accessCondition, Blob opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1287,7 +1414,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1357,7 +1484,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1456,7 +1583,7 @@ protected final int downloadRangeInternal(final long blobOffset, final Long leng opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) { throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER); @@ -1559,7 +1686,7 @@ public final int downloadToByteArray(final byte[] buffer, final int bufferOffet) * * @param buffer * A byte array which represents the buffer to which the blob bytes are downloaded. - * @param bufferOffet + * @param bufferOffset * A long which represents the byte offset to use as the starting point for the target. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. @@ -1594,7 +1721,7 @@ public final int downloadToByteArray(final byte[] buffer, final int bufferOffset } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToByteArrayImpl(null, null, buffer, bufferOffset, accessCondition, options, opContext), @@ -1636,7 +1763,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1745,7 +1872,7 @@ public void recoveryAction(OperationContext context) throws IOException { } /** - * Uploads a blob from data in a byte array. + * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten. * * @param buffer * A byte array which represents the data to write to the blob. @@ -1764,7 +1891,7 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int } /** - * Uploads a blob from data in a byte array. + * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten. * * @param buffer * A byte array which represents the data to write to the blob. @@ -1796,7 +1923,7 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int } /** - * Uploads a blob from a file. + * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten. * * @param path * A String which represents the path to the file to be uploaded. @@ -1810,7 +1937,7 @@ public void uploadFromFile(final String path) throws StorageException, IOExcepti } /** - * Uploads a blob from a file. + * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten. * * @param path * A String which represents the path to the file to be uploaded. @@ -1955,7 +2082,7 @@ private final boolean exists(final boolean primaryOnly, final AccessCondition ac } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1982,7 +2109,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -2061,7 +2188,7 @@ public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final SharedAccessBlobHeaders headers, final String groupPolicyIdentifier) throws InvalidKeyException, StorageException { - + if (!StorageCredentialsHelper.canCredentialsSignRequest(this.blobServiceClient.getCredentials())) { throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY); } @@ -2072,10 +2199,10 @@ public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String resourceName = this.getCanonicalName(true); - final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlob(policy, headers, - groupPolicyIdentifier, resourceName, this.blobServiceClient, null); + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(policy, headers, + groupPolicyIdentifier, resourceName, this.blobServiceClient); - final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlob(policy, + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(policy, headers, groupPolicyIdentifier, "b", signature); return builder.toString(); @@ -2083,32 +2210,34 @@ public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, /** * Returns the canonical name of the blob in the format of - * /<account-name>/<container-name>/<blob-name>. + * /<service-name>/<account-name>/<container-name>/<blob-name>. *

- * This format is used by both Shared Access and Copy blob operations. + * This format is used for Shared Access operations. * * @param ignoreSnapshotTime * true if the snapshot time is ignored; otherwise, false. * - * @return The canonical name in the format of /<account-name>/<container - * -name>/<blob-name>. + * @return The canonical name in the format of /<service-name>/<account-name> + * /<container-name>/<blob-name>. */ String getCanonicalName(final boolean ignoreSnapshotTime) { - String canonicalName; + StringBuilder canonicalName = new StringBuilder("/"); + canonicalName.append(SR.BLOB); + if (this.blobServiceClient.isUsePathStyleUris()) { - canonicalName = this.getUri().getRawPath(); + canonicalName.append(this.getUri().getRawPath()); } else { - canonicalName = PathUtility.getCanonicalPathFromCredentials(this.blobServiceClient.getCredentials(), this - .getUri().getRawPath()); + canonicalName.append(PathUtility.getCanonicalPathFromCredentials( + this.blobServiceClient.getCredentials(), this.getUri().getRawPath())); } if (!ignoreSnapshotTime && this.snapshotID != null) { - canonicalName = canonicalName.concat("?snapshot="); - canonicalName = canonicalName.concat(this.snapshotID); + canonicalName.append("?snapshot="); + canonicalName.append(this.snapshotID); } - return canonicalName; + return canonicalName.toString(); } /** @@ -2120,6 +2249,7 @@ String getCanonicalName(final boolean ignoreSnapshotTime) { * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") @Override public final CloudBlobContainer getContainer() throws StorageException, URISyntaxException { if (this.container == null) { @@ -2149,9 +2279,6 @@ public final HashMap getMetadata() { * If the resource URI is invalid. */ public final String getName() throws URISyntaxException { - if (Utility.isNullOrEmpty(this.name)) { - this.name = PathUtility.getBlobNameFromURI(this.getUri(), this.blobServiceClient.isUsePathStyleUris()); - } return this.name; } @@ -2327,7 +2454,7 @@ public final boolean isSnapshot() { /** * Opens a blob input stream to download the blob. *

- * Use {@link CloudBlobClient#setStreamMinimumReadSizeInBytes} to configure the read size. + * Use {@link #setStreamMinimumReadSizeInBytes(int)} to configure the read size. * * @return An InputStream object that represents the stream to use for reading from the blob. * @@ -2342,7 +2469,7 @@ public final BlobInputStream openInputStream() throws StorageException { /** * Opens a blob input stream to download the blob using the specified request options and operation context. *

- * Use {@link CloudBlobClient#setStreamMinimumReadSizeInBytes} to configure the read size. + * Use {@link #setStreamMinimumReadSizeInBytes(int)} to configure the read size. * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. @@ -2369,70 +2496,54 @@ public final BlobInputStream openInputStream(final AccessCondition accessConditi assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient, + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient, false /* setStartTime */); return new BlobInputStream(this, accessCondition, options, opContext); } - + /** - * Parse Uri for SAS (Shared access signature) information. - * - * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding - * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise - * a new client is created based on SAS information or as anonymous credentials. - * + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. + * * @param completeUri - * A {@link StorageUri} object which represents the complete Uri. - * @param existingClient - * A {@link CloudBlobClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise, false. + * A {@link StorageUri} object which represents the complete URI. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * */ - protected void parseURIQueryStringAndVerify(final StorageUri completeUri, final CloudBlobClient existingClient, - final boolean usePathStyleUris) throws StorageException { - Utility.assertNotNull("resourceUri", completeUri); + */ + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { + Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper - .parseQuery(queryParameters); - final String[] snapshotIDs = queryParameters.get(BlobConstants.SNAPSHOT); if (snapshotIDs != null && snapshotIDs.length > 0) { this.snapshotID = snapshotIDs[0]; } + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(queryParameters); - if (sasCreds == null && existingClient != null) { - return; + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); } - final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, - existingClient.getCredentials()); - - if (existingClient == null || !sameCredentials) { - try { - this.blobServiceClient = new CloudBlobClient((PathUtility.getServiceClientBaseAddress( - this.getStorageUri(), usePathStyleUris)), sasCreds); - } - catch (final URISyntaxException e) { - throw Utility.generateNewUnexpectedStorageException(e); - } + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.blobServiceClient = new CloudBlobClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getBlobNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris); } - - if (existingClient != null && !sameCredentials) { - this.blobServiceClient.setDefaultRequestOptions(new BlobRequestOptions(existingClient - .getDefaultRequestOptions())); - this.blobServiceClient.setDirectoryDelimiter(existingClient.getDirectoryDelimiter()); + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); } } @@ -2480,7 +2591,7 @@ public final void releaseLease(final AccessCondition accessCondition, BlobReques } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.releaseLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2502,7 +2613,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2567,7 +2678,7 @@ public final void renewLease(final AccessCondition accessCondition, BlobRequestO } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.renewLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2588,7 +2699,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2704,7 +2815,7 @@ protected void updateLengthFromResponse(HttpURLConnection request) { } /** - * Uploads the source stream data to the blob. + * Uploads the source stream data to the blob. If the blob already exists on the service, it will be overwritten. * * @param sourceStream * An InputStream object that represents the source stream to upload. @@ -2726,7 +2837,8 @@ protected void updateLengthFromResponse(HttpURLConnection request) { /** * Uploads the source stream data to the blob using the specified lease ID, request options, and operation context. - * + * If the blob already exists on the service, it will be overwritten. + * * @param sourceStream * An InputStream object that represents the source stream to upload. * @param length @@ -2798,7 +2910,7 @@ public final void uploadMetadata(final AccessCondition accessCondition, BlobRequ } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2824,7 +2936,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2887,7 +2999,7 @@ public final void uploadProperties(final AccessCondition accessCondition, BlobRe } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.uploadPropertiesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2909,7 +3021,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobClient.java index c8febc24ad259..05841ca61b1fe 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobClient.java @@ -19,12 +19,10 @@ import java.net.URISyntaxException; import com.microsoft.azure.storage.DoesServiceRequest; -import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultContinuationType; import com.microsoft.azure.storage.ResultSegment; -import com.microsoft.azure.storage.RetryExponentialRetry; import com.microsoft.azure.storage.ServiceClient; import com.microsoft.azure.storage.ServiceProperties; import com.microsoft.azure.storage.ServiceStats; @@ -56,7 +54,7 @@ public final class CloudBlobClient extends ServiceClient { /** * Holds the default request option values associated with this Service Client. */ - private BlobRequestOptions defaultRequestOptions; + private BlobRequestOptions defaultRequestOptions = new BlobRequestOptions(); /** * Creates an instance of the CloudBlobClient class using the specified Blob service endpoint and @@ -108,26 +106,7 @@ public CloudBlobClient(final URI baseUri, StorageCredentials credentials) { */ public CloudBlobClient(final StorageUri storageUri, StorageCredentials credentials) { super(storageUri, credentials); - this.defaultRequestOptions = new BlobRequestOptions(); - this.defaultRequestOptions.setLocationMode(LocationMode.PRIMARY_ONLY); - this.defaultRequestOptions.setRetryPolicyFactory(new RetryExponentialRetry()); - this.defaultRequestOptions.setConcurrentRequestCount(BlobConstants.DEFAULT_CONCURRENT_REQUEST_COUNT); - this.defaultRequestOptions.setDisableContentMD5Validation(false); - this.defaultRequestOptions - .setSingleBlobPutThresholdInBytes(BlobConstants.DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES); - this.defaultRequestOptions.setUseTransactionalContentMD5(false); - } - - /** - * Returns the number of maximum concurrent requests allowed. - * - * @return The number of maximum concurrent requests allowed. - * - * @deprecated use {@link #getDefaultRequestOptions().getConcurrentRequestCount()} instead. - */ - @Deprecated - public int getConcurrentRequestCount() { - return this.defaultRequestOptions.getConcurrentRequestCount(); + BlobRequestOptions.applyDefaults(this.defaultRequestOptions, BlobType.UNSPECIFIED); } /** @@ -149,6 +128,7 @@ public int getConcurrentRequestCount() { * @see Naming and Referencing Containers, Blobs, * and Metadata */ + @SuppressWarnings("deprecation") public CloudBlobContainer getContainerReference(final String containerName) throws URISyntaxException, StorageException { return new CloudBlobContainer(containerName, this); @@ -163,21 +143,6 @@ public String getDirectoryDelimiter() { return this.directoryDelimiter; } - /** - * Returns the threshold size used for writing a single blob for this Blob service client. - * - * @return The maximum size, in bytes, of a blob that may be uploaded as a single blob, ranging from 1 to 64 MB - * inclusive. The default value is 32 MBs. - *

- * If a blob size is above the threshold, it will be uploaded as blocks. - * - * @deprecated use {@link #getDefaultRequestOptions().getSingleBlobPutThresholdInBytes()} instead. - */ - @Deprecated - public int getSingleBlobPutThresholdInBytes() { - return this.getDefaultRequestOptions().getSingleBlobPutThresholdInBytes(); - } - /** * Returns an enumerable collection of blob containers for this Blob service client. * @@ -336,7 +301,7 @@ private Iterable listContainersWithPrefix(final String prefi } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); @@ -384,7 +349,7 @@ private ResultSegment listContainersWithPrefixSegmented(fina } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this); Utility.assertContinuationType(continuationToken, ResultContinuationType.CONTAINER); @@ -424,7 +389,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, Void parentObject, @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -500,7 +465,7 @@ public ServiceStats getServiceStats(BlobRequestOptions options, OperationContext } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this); return ExecutionEngine.executeWithRetry(this, null, this.getServiceStatsImpl(options, false), options.getRetryPolicyFactory(), opContext); @@ -546,7 +511,7 @@ public final ServiceProperties downloadServiceProperties(BlobRequestOptions opti } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this); return ExecutionEngine.executeWithRetry(this, null, this.downloadServicePropertiesImpl(options, false), options.getRetryPolicyFactory(), opContext); @@ -593,7 +558,7 @@ public void uploadServiceProperties(final ServiceProperties properties, BlobRequ } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this); Utility.assertNotNull("properties", properties); @@ -602,20 +567,6 @@ public void uploadServiceProperties(final ServiceProperties properties, BlobRequ options.getRetryPolicyFactory(), opContext); } - /** - * Sets the maximum number of concurrent requests allowed for the Blob service client. - * - * @param concurrentRequestCount - * The value being assigned as the maximum number of concurrent requests allowed for the Blob service - * client. - * - * @deprecated use {@link #getDefaultRequestOptions().setConcurrentRequestCount()} instead. - */ - @Deprecated - public void setConcurrentRequestCount(final int concurrentRequestCount) { - this.defaultRequestOptions.setConcurrentRequestCount(concurrentRequestCount); - } - /** * Sets the value for the default delimiter used for cloud blob directories. * @@ -627,25 +578,6 @@ public void setDirectoryDelimiter(final String directoryDelimiter) { this.directoryDelimiter = directoryDelimiter; } - /** - * Sets the threshold size used for writing a single blob to use with this Blob service client. - * - * @param singleBlobPutThresholdInBytes - * An int which specifies the maximum size, in bytes, of a blob that may be uploaded as a - * single - * blob, ranging from 1 MB to 64 MB inclusive. If a blob size is above the threshold, it will be uploaded - * as blocks. - * - * @throws IllegalArgumentException - * If minimumReadSize is less than 1 MB or greater than 64 MB. - * - * @deprecated use {@link #getDefaultRequestOptions().setSingleBlobPutThresholdInBytes()} instead. - */ - @Deprecated - public void setSingleBlobPutThresholdInBytes(final int singleBlobPutThresholdInBytes) { - this.defaultRequestOptions.setSingleBlobPutThresholdInBytes(singleBlobPutThresholdInBytes); - } - /** * Gets the {@link BlobRequestOptions} that is used for requests associated with this CloudBlobClient * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java index 1dea8bf3710e3..73cd903a3a569 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java @@ -37,6 +37,7 @@ import com.microsoft.azure.storage.ResultSegment; import com.microsoft.azure.storage.SharedAccessPolicyHandler; import com.microsoft.azure.storage.SharedAccessPolicySerializer; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; @@ -94,12 +95,12 @@ else if ("blob".equals(lowerAclString)) { /** * Represents the container metadata. */ - protected HashMap metadata; + protected HashMap metadata = new HashMap(); /** * Holds the container properties. */ - BlobContainerProperties properties; + BlobContainerProperties properties = new BlobContainerProperties(); /** * Holds the name of the container. @@ -115,19 +116,7 @@ else if ("blob".equals(lowerAclString)) { * Holds a reference to the associated service client. */ private CloudBlobClient blobServiceClient; - - /** - * Initializes a new instance of the CloudBlobContainer class. - * - * @param client - * A {@link CloudBlobClient} which represents a reference to the associated service client. - */ - private CloudBlobContainer(final CloudBlobClient client) { - this.metadata = new HashMap(); - this.properties = new BlobContainerProperties(); - this.blobServiceClient = client; - } - + /** * Creates an instance of the CloudBlobContainer class using the specified URI. The blob URI should * include a SAS token unless anonymous access is to be used. @@ -137,10 +126,8 @@ private CloudBlobContainer(final CloudBlobClient client) { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudBlobContainer(final URI uri) throws URISyntaxException, StorageException { + public CloudBlobContainer(final URI uri) throws StorageException { this(new StorageUri(uri)); } @@ -153,11 +140,40 @@ public CloudBlobContainer(final URI uri) throws URISyntaxException, StorageExcep * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudBlobContainer(final StorageUri storageUri) throws URISyntaxException, StorageException { - this(storageUri, (CloudBlobClient) null /* client */); + public CloudBlobContainer(final StorageUri storageUri) throws StorageException { + this(storageUri, (StorageCredentials) null); + } + + /** + * Creates an instance of the CloudBlobContainer class using the specified URI and credentials. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the container. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlobContainer(final URI uri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(uri), credentials); + } + + /** + * Creates an instance of the CloudBlobContainer class using the specified StorageUri and credentials. + * + * @param storageUri + * A {@link StorageUri} object which represents the absolute StorageUri of the container. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlobContainer(final StorageUri storageUri, final StorageCredentials credentials) + throws StorageException { + this.parseQueryAndVerify(storageUri, credentials); } /** @@ -180,19 +196,19 @@ public CloudBlobContainer(final StorageUri storageUri) throws URISyntaxException * * @see Naming and Referencing Containers, Blobs, * and Metadata + * @deprecated as of 3.0.0. Please use {@link CloudBlobClient#getContainerReference(String)} */ + @Deprecated public CloudBlobContainer(final String containerName, final CloudBlobClient client) throws URISyntaxException, StorageException { - this(client); Utility.assertNotNull("client", client); Utility.assertNotNull("containerName", containerName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), containerName); - this.name = containerName; - this.parseQueryAndVerify(this.storageUri, client, client.isUsePathStyleUris()); + this.blobServiceClient = client; } - + /** * Creates an instance of the CloudBlobContainer class using the specified URI and client. * @@ -206,7 +222,9 @@ public CloudBlobContainer(final String containerName, final CloudBlobClient clie * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudBlobContainer#CloudBlobContainer(URI, StorageCredentials)} */ + @Deprecated public CloudBlobContainer(final URI uri, final CloudBlobClient client) throws URISyntaxException, StorageException { this(new StorageUri(uri), client); } @@ -224,21 +242,17 @@ public CloudBlobContainer(final URI uri, final CloudBlobClient client) throws UR * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudBlobContainer#CloudBlobContainer(StorageUri, StorageCredentials)} */ + @Deprecated public CloudBlobContainer(final StorageUri storageUri, final CloudBlobClient client) throws URISyntaxException, StorageException { - this(client); - - Utility.assertNotNull("storageUri", storageUri); - - this.storageUri = storageUri; - - boolean usePathStyleUris = client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) - : client.isUsePathStyleUris(); - - this.name = PathUtility.getContainerNameFromUri(storageUri.getPrimaryUri(), usePathStyleUris); + this.parseQueryAndVerify(storageUri, client == null ? null : client.getCredentials()); - this.parseQueryAndVerify(this.storageUri, client, usePathStyleUris); + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.blobServiceClient = client; + } } /** @@ -274,7 +288,7 @@ public void create(BlobRequestOptions options, OperationContext opContext) throw } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, createImpl(options), options.getRetryPolicyFactory(), opContext); @@ -300,7 +314,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlobContainer containe @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -355,7 +369,7 @@ public boolean createIfNotExists() throws StorageException { */ @DoesServiceRequest public boolean createIfNotExists(BlobRequestOptions options, OperationContext opContext) throws StorageException { - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext); if (exists) { @@ -414,7 +428,7 @@ public void delete(AccessCondition accessCondition, BlobRequestOptions options, } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, deleteImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -434,7 +448,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -486,7 +500,7 @@ public boolean deleteIfExists() throws StorageException { @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); if (exists) { @@ -546,7 +560,7 @@ public void downloadAttributes(AccessCondition accessCondition, BlobRequestOptio } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -573,7 +587,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -637,7 +651,7 @@ public BlobContainerPermissions downloadPermissions(AccessCondition accessCondit } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, downloadPermissionsImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -663,7 +677,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -744,7 +758,7 @@ private boolean exists(final boolean primaryOnly, final AccessCondition accessCo } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -772,7 +786,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -822,34 +836,74 @@ public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String resourceName = this.getSharedAccessCanonicalName(); - final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlob(policy, - null /* SharedAccessBlobHeaders */, groupPolicyIdentifier, resourceName, this.blobServiceClient, null); + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(policy, + null /* SharedAccessBlobHeaders */, groupPolicyIdentifier, resourceName, this.blobServiceClient); - final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlob(policy, + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(policy, null /* SharedAccessBlobHeaders */, groupPolicyIdentifier, "c", signature); return builder.toString(); } /** - * Returns a reference to a {@link CloudBlockBlob} object that represents a block blob in this container. + * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in this container. * * @param blobName * A String that represents the name of the blob. * - * @return A {@link CloudBlockBlob} object that represents a reference to the specified block blob. + * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. */ - public CloudBlockBlob getBlockBlobReference(final String blobName) throws URISyntaxException, StorageException { + public CloudAppendBlob getAppendBlobReference(final String blobName) throws URISyntaxException, StorageException { + return this.getAppendBlobReference(blobName, null); + } + + /** + * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in the container, using the + * specified snapshot ID. + * + * @param blobName + * A String that represents the name of the blob. + * @param snapshotID + * A String that represents the snapshot ID of the blob. + * + * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudAppendBlob getAppendBlobReference(final String blobName, final String snapshotID) + throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("blobName", blobName); final StorageUri address = PathUtility.appendPathToUri(this.storageUri, blobName); - return new CloudBlockBlob(address, this.blobServiceClient, this); + final CloudAppendBlob retBlob = new CloudAppendBlob(address, snapshotID, this.blobServiceClient); + retBlob.setContainer(this); + return retBlob; + } + + /** + * Returns a reference to a {@link CloudBlockBlob} object that represents a block blob in this container. + * + * @param blobName + * A String that represents the name of the blob. + * + * @return A {@link CloudBlockBlob} object that represents a reference to the specified block blob. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudBlockBlob getBlockBlobReference(final String blobName) throws URISyntaxException, StorageException { + return this.getBlockBlobReference(blobName, null); } /** @@ -868,6 +922,7 @@ public CloudBlockBlob getBlockBlobReference(final String blobName) throws URISyn * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") public CloudBlockBlob getBlockBlobReference(final String blobName, final String snapshotID) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("blobName", blobName); @@ -945,15 +1000,11 @@ public StorageUri getStorageUri() { * If the resource URI is invalid. */ public CloudPageBlob getPageBlobReference(final String blobName) throws URISyntaxException, StorageException { - Utility.assertNotNullOrEmpty("blobName", blobName); - - final StorageUri address = PathUtility.appendPathToUri(this.storageUri, blobName); - - return new CloudPageBlob(address, this.blobServiceClient, this); + return this.getPageBlobReference(blobName, null); } /** - * Returns a reference to a {@link CloudPageBlob} object that represents a page blob in the directory, using the + * Returns a reference to a {@link CloudPageBlob} object that represents a page blob in the container, using the * specified snapshot ID. * * @param blobName @@ -968,6 +1019,7 @@ public CloudPageBlob getPageBlobReference(final String blobName) throws URISynta * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") public CloudPageBlob getPageBlobReference(final String blobName, final String snapshotID) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("blobName", blobName); @@ -1006,7 +1058,7 @@ private String getSharedAccessCanonicalName() { String accountName = this.getServiceClient().getCredentials().getAccountName(); String containerName = this.getName(); - return String.format("/%s/%s", accountName, containerName); + return String.format("/%s/%s/%s", SR.BLOB, accountName, containerName); } /** @@ -1095,7 +1147,7 @@ public Iterable listBlobs(final String prefix, final boolean useFl } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); if (!useFlatBlobListing && listingDetails != null && listingDetails.contains(BlobListingDetails.SNAPSHOTS)) { throw new IllegalArgumentException(SR.SNAPSHOT_LISTING_ERROR); @@ -1186,7 +1238,7 @@ public ResultSegment listBlobsSegmented(final String prefix, final } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); Utility.assertContinuationType(continuationToken, ResultContinuationType.BLOB); @@ -1233,7 +1285,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1406,58 +1458,40 @@ public ResultSegment listContainersSegmented(final String pr } /** - * Parse URI for SAS (Shared access signature) information. - * - * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding - * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise - * a new client is created based on SAS information or as anonymous credentials. + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. * * @param completeUri * A {@link StorageUri} object which represents the complete URI. - * @param existingClient - * A {@link CloudBlobClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise, false. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - private void parseQueryAndVerify(final StorageUri completeUri, final CloudBlobClient existingClient, - final boolean usePathStyleUris) throws URISyntaxException, StorageException { - Utility.assertNotNull("completeUri", completeUri); + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { + Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper - .parseQuery(queryParameters); - - if (sasCreds == null) { - if (existingClient == null) { - this.blobServiceClient = new CloudBlobClient(new URI(PathUtility.getServiceClientBaseAddress( - this.getUri(), usePathStyleUris))); - } - return; + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); } - final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, - existingClient.getCredentials()); - - if (existingClient == null || !sameCredentials) { - this.blobServiceClient = new CloudBlobClient(new URI(PathUtility.getServiceClientBaseAddress(this.getUri(), - usePathStyleUris)), sasCreds); + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.blobServiceClient = new CloudBlobClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getContainerNameFromUri(this.storageUri.getPrimaryUri(), usePathStyleUris); } - - if (existingClient != null && !sameCredentials) { - this.blobServiceClient.setDefaultRequestOptions(new BlobRequestOptions(existingClient - .getDefaultRequestOptions())); - this.blobServiceClient.setDirectoryDelimiter(existingClient.getDirectoryDelimiter()); + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); } } @@ -1486,16 +1520,6 @@ public void setMetadata(final HashMap metadata) { this.metadata = metadata; } - /** - * Sets the name of the container. - * - * @param name - * A String that represents the name being assigned to the container. - */ - protected void setName(final String name) { - this.name = name; - } - /** * Sets the list of URIs for all locations. * @@ -1553,7 +1577,7 @@ public void uploadMetadata(AccessCondition accessCondition, BlobRequestOptions o } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1582,7 +1606,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlobContainer containe @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -1641,7 +1665,7 @@ public void uploadPermissions(final BlobContainerPermissions permissions, final } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, uploadPermissionsImpl(permissions, accessCondition, options), options.getRetryPolicyFactory(), @@ -1670,7 +1694,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, context); } @Override @@ -1704,6 +1728,19 @@ public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient cli } } + /** + * Acquires a new infinite lease on the container. + * + * @return A String that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease() throws StorageException { + return this.acquireLease(null /* leaseTimeInSeconds */, null /* proposedLeaseId */); + } + /** * Acquires a new lease on the container with the specified lease time and proposed lease ID. * @@ -1768,7 +1805,7 @@ public final String acquireLease(final Integer leaseTimeInSeconds, final String } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.acquireLeaseImpl(leaseTimeInSeconds, proposedLeaseId, accessCondition, options), @@ -1792,7 +1829,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -1859,7 +1896,7 @@ public final void renewLease(final AccessCondition accessCondition, BlobRequestO } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.renewLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1881,7 +1918,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -1947,7 +1984,7 @@ public final void releaseLease(final AccessCondition accessCondition, BlobReques } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.releaseLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1968,7 +2005,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2043,7 +2080,7 @@ public final long breakLease(final Integer breakPeriodInSeconds, final AccessCon } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.breakLeaseImpl(breakPeriodInSeconds, accessCondition, options), options.getRetryPolicyFactory(), @@ -2065,7 +2102,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2143,7 +2180,7 @@ public final String changeLease(final String proposedLeaseId, final AccessCondit } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.changeLeaseImpl(proposedLeaseId, accessCondition, options), options.getRetryPolicyFactory(), @@ -2165,7 +2202,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobDirectory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobDirectory.java index 79b73898b04a1..c147bbbc1f4f4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobDirectory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobDirectory.java @@ -110,6 +110,52 @@ protected CloudBlobDirectory(final StorageUri uri, final String prefix, final Cl this.storageUri = uri; } + /** + * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in the directory. + * + * @param blobName + * A String that represents the name of the blob. + * + * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudAppendBlob getAppendBlobReference(final String blobName) throws URISyntaxException, StorageException { + return this.getAppendBlobReference(blobName, null); + } + + /** + * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in the directory, using the + * specified snapshot ID. + * + * @param blobName + * A String that represents the name of the blob. + * @param snapshotID + * A String that represents the snapshot ID of the blob. + * + * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudAppendBlob getAppendBlobReference(final String blobName, final String snapshotID) + throws URISyntaxException, StorageException { + Utility.assertNotNullOrEmpty("blobName", blobName); + + final StorageUri address = PathUtility.appendPathToUri(this.storageUri, blobName, + this.blobServiceClient.getDirectoryDelimiter()); + + final CloudAppendBlob retBlob = new CloudAppendBlob(address, snapshotID, this.blobServiceClient); + retBlob.setContainer(this.container); + + return retBlob; + } + /** * Returns a reference to a {@link CloudBlockBlob} object that represents a block blob in this directory. * @@ -143,6 +189,7 @@ public CloudBlockBlob getBlockBlobReference(final String blobName) throws URISyn * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") public CloudBlockBlob getBlockBlobReference(final String blobName, final String snapshotID) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("blobName", blobName); @@ -176,7 +223,7 @@ public CloudBlobContainer getContainer() throws StorageException, URISyntaxExcep * @param blobName * A String that represents the name of the blob. * - * @return A {@link CloudBlockBlob} object that represents a reference to the specified page blob. + * @return A {@link CloudPageBlob} object that represents a reference to the specified page blob. * * @throws StorageException * If a storage service error occurred. @@ -196,13 +243,14 @@ public CloudPageBlob getPageBlobReference(final String blobName) throws URISynta * @param snapshotID * A String that represents the snapshot ID of the blob. * - * @return A {@link CloudBlockBlob} object that represents a reference to the specified page blob. + * @return A {@link CloudPageBlob} object that represents a reference to the specified page blob. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") public CloudPageBlob getPageBlobReference(final String blobName, final String snapshotID) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("blobName", blobName); @@ -282,24 +330,6 @@ public CloudBlobDirectory getDirectoryReference(String directoryName) throws URI return new CloudBlobDirectory(address, subDirName, this.blobServiceClient, this.container, this); } - - /** - * Returns a reference to a virtual blob directory beneath this directory. - * - * @param directoryName - * A String that represents the name of the virtual directory. - * - * @return A CloudBlobDirectory object that represents a virtual blob directory beneath this directory. - * - * @throws URISyntaxException - * If the resource URI is invalid. - * - * @deprecated as of 2.0.0. Use {@link #getDirectoryReference()} instead. - */ - @Deprecated - public CloudBlobDirectory getSubDirectoryReference(String directoryName) throws URISyntaxException { - return this.getDirectoryReference(directoryName); - } /** * Returns the URI for this directory. @@ -471,7 +501,7 @@ public ResultSegment listBlobsSegmented(String prefix) throws Stor */ @DoesServiceRequest public ResultSegment listBlobsSegmented(String prefix, final boolean useFlatBlobListing, - final EnumSet listingDetails, final int maxResults, + final EnumSet listingDetails, final Integer maxResults, final ResultContinuation continuationToken, final BlobRequestOptions options, final OperationContext opContext) throws StorageException, URISyntaxException { prefix = prefix == null ? Constants.EMPTY_STRING : prefix; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java index dc1102423853d..fbeb56e3161ec 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import javax.xml.stream.XMLStreamException; @@ -28,6 +29,7 @@ import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; @@ -38,6 +40,7 @@ import com.microsoft.azure.storage.core.StorageRequest; import com.microsoft.azure.storage.core.StreamMd5AndLength; import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.file.CloudFile; /** * Represents a blob that is uploaded as a set of blocks. @@ -67,12 +70,7 @@ public CloudBlockBlob(final URI blobAbsoluteUri) throws StorageException { * If a storage service error occurred. */ public CloudBlockBlob(final StorageUri blobAbsoluteUri) throws StorageException { - super(BlobType.BLOCK_BLOB); - - Utility.assertNotNull("blobAbsoluteUri", blobAbsoluteUri); - this.setStorageUri(blobAbsoluteUri); - this.parseURIQueryStringAndVerify(blobAbsoluteUri, null, - Utility.determinePathStyleFromUri(blobAbsoluteUri.getPrimaryUri())); + this(blobAbsoluteUri, (StorageCredentials)null); } /** @@ -85,6 +83,72 @@ public CloudBlockBlob(final CloudBlockBlob otherBlob) { super(otherBlob); } + /** + * Creates an instance of the CloudBlockBlob class using the specified absolute URI and credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final URI blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(blobAbsoluteUri), credentials); + } + + /** + * Creates an instance of the CloudBlockBlob class using the specified absolute StorageUri and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute StorageUri to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final StorageUri blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(blobAbsoluteUri, null /* snapshotID */, credentials); + } + + /** + * Creates an instance of the CloudBlockBlob class using the specified absolute URI, snapshot ID, and + * credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final URI blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + this(new StorageUri(blobAbsoluteUri), snapshotID, credentials); + } + + /** + * Creates an instance of the CloudBlockBlob class using the specified absolute StorageUri, snapshot + * ID, and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute StorageUri to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + super(BlobType.BLOCK_BLOB, blobAbsoluteUri, snapshotID, credentials); + } + /** * Creates an instance of the CloudBlockBlob class using the specified absolute URI and storage service * client. @@ -96,7 +160,9 @@ public CloudBlockBlob(final CloudBlockBlob otherBlob) { * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(URI, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final URI blobAbsoluteUri, final CloudBlobClient client) throws StorageException { this(new StorageUri(blobAbsoluteUri), client); } @@ -112,7 +178,9 @@ public CloudBlockBlob(final URI blobAbsoluteUri, final CloudBlobClient client) t * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(StorageUri, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient client) throws StorageException { super(BlobType.BLOCK_BLOB, blobAbsoluteUri, client); } @@ -130,7 +198,9 @@ public CloudBlockBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient cl * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(URI, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final URI blobAbsoluteUri, final CloudBlobClient client, final CloudBlobContainer container) throws StorageException { this(new StorageUri(blobAbsoluteUri), client, container); @@ -149,7 +219,9 @@ public CloudBlockBlob(final URI blobAbsoluteUri, final CloudBlobClient client, f * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(StorageUri, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient client, final CloudBlobContainer container) throws StorageException { super(BlobType.BLOCK_BLOB, blobAbsoluteUri, client, container); @@ -168,7 +240,9 @@ public CloudBlockBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient cl * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(URI, String, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final URI blobAbsoluteUri, final String snapshotID, final CloudBlobClient client) throws StorageException { this(new StorageUri(blobAbsoluteUri), snapshotID, client); @@ -187,11 +261,120 @@ public CloudBlockBlob(final URI blobAbsoluteUri, final String snapshotID, final * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudBlockBlob#CloudBlockBlob(StorageUri, String, StorageCredentials)} */ + @Deprecated public CloudBlockBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final CloudBlobClient client) throws StorageException { super(BlobType.BLOCK_BLOB, blobAbsoluteUri, snapshotID, client); } + + /** + * Requests the service to start copying a block blob's contents, properties, and metadata to a new block blob. + * + * @param sourceBlob + * A CloudBlockBlob object that represents the source blob to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudBlockBlob sourceBlob) throws StorageException, URISyntaxException { + return this.startCopy(sourceBlob, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a block blob's contents, properties, and metadata to a new block blob, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param sourceBlob + * A CloudBlockBlob object that represents the source blob to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudBlockBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceBlob", sourceBlob); + return this.startCopy( + sourceBlob.getQualifiedUri(), sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Requests the service to start copying a file's contents, properties, and metadata to a new block blob. + * + * @param sourceFile + * A CloudFile object that represents the source file to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudFile sourceFile) throws StorageException, URISyntaxException { + return this.startCopy(sourceFile, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a file's contents, properties, and metadata to a new block blob, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param sourceFile + * A CloudFile object that represents the source file to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source file. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination block blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + @DoesServiceRequest + public final String startCopy(final CloudFile sourceFile, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceFile", sourceFile); + return this.startCopy( + sourceFile.getServiceClient().getCredentials().transformUri(sourceFile.getUri()), + sourceAccessCondition, destinationAccessCondition, options, opContext); + } /** * Commits a block list to the storage service. In order to be written as part of a blob, a block must have been @@ -240,7 +423,7 @@ public void commitBlockList(final Iterable blockList, final AccessCo opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.commitBlockListImpl(blockList, accessCondition, options, opContext), @@ -286,7 +469,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, this.getLength(), null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, this.getLength(), context); } @Override @@ -380,7 +563,7 @@ public ArrayList downloadBlockList(final BlockListingFilter blockLis } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadBlockListImpl(blockListingFilter, accessCondition, options), @@ -408,7 +591,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -436,7 +619,12 @@ public ArrayList postProcessResponse(HttpURLConnection connection, C } /** - * Creates and opens an output stream to write data to the block blob. + * Creates and opens an output stream to write data to the block blob. If the blob already exists on the service, it + * will be overwritten. + *

+ * To avoid overwriting and instead throw an error, please use the + * {@link #openOutputStream(AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate + * {@link AccessCondition}. * * @return A {@link BlobOutputStream} object used to write data to the blob. * @@ -449,7 +637,10 @@ public BlobOutputStream openOutputStream() throws StorageException { /** * Creates and opens an output stream to write data to the block blob using the specified request options and - * operation context. + * operation context. If the blob already exists on the service, it will be overwritten. + *

+ * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. @@ -467,7 +658,7 @@ public BlobOutputStream openOutputStream() throws StorageException { * @throws StorageException * If a storage service error occurred. */ - public BlobOutputStream openOutputStream(final AccessCondition accessCondition, BlobRequestOptions options, + public BlobOutputStream openOutputStream(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); @@ -475,14 +666,23 @@ public BlobOutputStream openOutputStream(final AccessCondition accessCondition, assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient, + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient, false /* setStartTime */); + + // Apply any conditional access conditions up front + if (accessCondition != null && + (accessCondition.getIfMatch() != null || accessCondition.getIfNoneMatch() != null + || accessCondition.getIfModifiedSinceDate() != null + || accessCondition.getIfUnmodifiedSinceDate() != null)) { + this.downloadAttributes(accessCondition, options, opContext); + } return new BlobOutputStream(this, accessCondition, options, opContext); } /** - * Uploads the source stream data to the block blob. + * Uploads the source stream data to the block blob. If the blob already exists on the service, it will be + * overwritten. * * @param sourceStream * An {@link InputStream} object that represents the input stream to write to the block blob. @@ -502,6 +702,7 @@ public void upload(final InputStream sourceStream, final long length) throws Sto /** * Uploads the source stream data to the blob, using the specified lease ID, request options, and operation context. + * If the blob already exists on the service, it will be overwritten. * * @param sourceStream * An {@link InputStream} object that represents the input stream to write to the block blob. @@ -538,7 +739,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); StreamMd5AndLength descriptor = new StreamMd5AndLength(); descriptor.setLength(length); @@ -648,7 +849,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context); } @Override @@ -739,17 +940,13 @@ public void uploadBlock(final String blockId, final InputStream sourceStream, fi throw new IllegalArgumentException(SR.STREAM_LENGTH_NEGATIVE); } - if (length > 4 * Constants.MB) { - throw new IllegalArgumentException(SR.STREAM_LENGTH_GREATER_THAN_4MB); - } - assertNoWriteOperationForSnapshot(); if (opContext == null) { opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); // Assert block length if (Utility.isNullOrEmpty(blockId) || !Base64.validateIsBase64String(blockId)) { @@ -776,7 +973,6 @@ public void uploadBlock(final String blockId, final InputStream sourceStream, fi else if (length < 0 || options.getUseTransactionalContentMD5()) { // If the stream is of unknown length or we need to calculate the // MD5, then we we need to read the stream contents first - descriptor = Utility.analyzeStream(sourceStream, length, -1L, true /* rewindSourceStream */, options.getUseTransactionalContentMD5()); } @@ -844,7 +1040,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context); } @Override @@ -869,7 +1065,8 @@ public void recoveryAction(OperationContext context) throws IOException { } /** - * Uploads a blob from a string using the platform's default encoding. + * Uploads a blob from a string using the platform's default encoding. If the blob already exists on the service, it + * will be overwritten. * * @param content * A String which represents the content that will be uploaded to the blob. @@ -883,7 +1080,8 @@ public void uploadText(final String content) throws StorageException, IOExceptio } /** - * Uploads a blob from a string using the specified encoding. + * Uploads a blob from a string using the specified encoding. If the blob already exists on the service, it will be + * overwritten. * * @param content * A String which represents the content that will be uploaded to the blob. @@ -959,7 +1157,7 @@ public String downloadText(final String charsetName, final AccessCondition acces /** * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream}. * - * @param writeBlockSizeInBytes + * @param streamWriteSizeInBytes * An int which represents the maximum block size, in bytes, for writing to a block blob * while using a {@link BlobOutputStream} object, ranging from 16 KB to 4 MB, inclusive. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java index 5483c12b8eb52..ddf8596712c60 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -27,6 +28,7 @@ import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.Base64; @@ -65,12 +67,7 @@ public CloudPageBlob(final URI blobAbsoluteUri) throws StorageException { * If a storage service error occurred. */ public CloudPageBlob(final StorageUri blobAbsoluteUri) throws StorageException { - super(BlobType.PAGE_BLOB); - - Utility.assertNotNull("blobAbsoluteUri", blobAbsoluteUri); - this.setStorageUri(blobAbsoluteUri); - this.parseURIQueryStringAndVerify(blobAbsoluteUri, null, - Utility.determinePathStyleFromUri(blobAbsoluteUri.getPrimaryUri()));; + this(blobAbsoluteUri, (StorageCredentials)null); } /** @@ -82,6 +79,72 @@ public CloudPageBlob(final StorageUri blobAbsoluteUri) throws StorageException { public CloudPageBlob(final CloudPageBlob otherBlob) { super(otherBlob); } + + /** + * Creates an instance of the CloudPageBlob class using the specified absolute URI and credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final URI blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(blobAbsoluteUri), credentials); + } + + /** + * Creates an instance of the CloudPageBlob class using the specified absolute URI, snapshot ID, and + * credentials. + * + * @param blobAbsoluteUri + * A java.net.URI object that represents the absolute URI to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final URI blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + this(new StorageUri(blobAbsoluteUri), snapshotID, credentials); + } + + /** + * Creates an instance of the CloudPageBlob class using the specified absolute StorageUri and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the blob. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final StorageUri blobAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(blobAbsoluteUri, null /* snapshotID */, credentials); + } + + /** + * Creates an instance of the CloudPageBlob class using the specified absolute StorageUri, snapshot + * ID, and credentials. + * + * @param blobAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the blob. + * @param snapshotID + * A String that represents the snapshot version, if applicable. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials) + throws StorageException { + super(BlobType.PAGE_BLOB, blobAbsoluteUri, snapshotID, credentials); + } /** * Creates an instance of the CloudPageBlob class using the specified URI and cloud blob client. @@ -93,7 +156,9 @@ public CloudPageBlob(final CloudPageBlob otherBlob) { * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(URI, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final URI blobAbsoluteUri, final CloudBlobClient client) throws StorageException { this(new StorageUri(blobAbsoluteUri), client); } @@ -108,7 +173,9 @@ public CloudPageBlob(final URI blobAbsoluteUri, final CloudBlobClient client) th * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(StorageUri, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient client) throws StorageException { super(BlobType.PAGE_BLOB, blobAbsoluteUri, client); } @@ -126,7 +193,9 @@ public CloudPageBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient cli * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(URI, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final URI blobAbsoluteUri, final CloudBlobClient client, final CloudBlobContainer container) throws StorageException { this(new StorageUri(blobAbsoluteUri), client, container); @@ -145,7 +214,9 @@ public CloudPageBlob(final URI blobAbsoluteUri, final CloudBlobClient client, fi * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(StorageUri, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient client, final CloudBlobContainer container) throws StorageException { super(BlobType.PAGE_BLOB, blobAbsoluteUri, client, container); @@ -164,7 +235,9 @@ public CloudPageBlob(final StorageUri blobAbsoluteUri, final CloudBlobClient cli * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(URI, String, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final URI blobAbsoluteUri, final String snapshotID, final CloudBlobClient client) throws StorageException { this(new StorageUri(blobAbsoluteUri), snapshotID, client); @@ -183,11 +256,66 @@ public CloudPageBlob(final URI blobAbsoluteUri, final String snapshotID, final C * * @throws StorageException * If a storage service error occurred. + * @deprecated as of 3.0.0. Please use {@link CloudPageBlob#CloudPageBlob(StorageUri, String, StorageCredentials)} */ + @Deprecated public CloudPageBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final CloudBlobClient client) throws StorageException { super(BlobType.PAGE_BLOB, blobAbsoluteUri, snapshotID, client); } + + /** + * Requests the service to start copying a blob's contents, properties, and metadata to a new blob. + * + * @param sourceBlob + * A CloudPageBlob object that represents the source blob to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudPageBlob sourceBlob) throws StorageException, URISyntaxException { + return this.startCopy(sourceBlob, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the + * specified access conditions, lease ID, request options, and operation context. + * + * @param sourceBlob + * A CloudPageBlob object that represents the source blob to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudPageBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceBlob", sourceBlob); + return this.startCopy( + sourceBlob.getQualifiedUri(), sourceAccessCondition, destinationAccessCondition, options, opContext); + } /** * Clears pages from a page blob. @@ -249,15 +377,16 @@ public void clearPages(final long offset, final long length, final AccessConditi opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); PageRange range = new PageRange(offset, offset + length - 1); this.putPagesInternal(range, PageOperationType.CLEAR, null, length, null, accessCondition, options, opContext); } /** - * Creates a page blob. - * + * Creates a page blob. If the blob already exists, this will replace it. To instead throw an error if the blob + * already exists, use the {@link #create(long, AccessCondition, BlobRequestOptions, OperationContext)} + * overload with {@link AccessCondition#generateIfNotExistsCondition()}. * @param length * A long which represents the size, in bytes, of the page blob. * @@ -272,7 +401,9 @@ public void create(final long length) throws StorageException { } /** - * Creates a page blob using the specified request options and operation context. + * Creates a page blob using the specified request options and operation context. If the blob already exists, + * this will replace it. To instead throw an error if the blob already exists, use + * {@link AccessCondition#generateIfNotExistsCondition()}. * * @param length * A long which represents the size, in bytes, of the page blob. @@ -306,7 +437,7 @@ public void create(final long length, final AccessCondition accessCondition, Blo opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.createImpl(length, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -332,7 +463,7 @@ public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -398,7 +529,7 @@ public ArrayList downloadPageRanges(final AccessCondition accessCondi opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadPageRangesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -424,7 +555,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -453,7 +584,8 @@ public ArrayList postProcessResponse(HttpURLConnection connection, Cl } /** - * Opens an output stream object to write data to the page blob. The page blob must already exist. + * Opens an output stream object to write data to the page blob. The page blob must already exist and any existing + * data may be overwritten. * * @return A {@link BlobOutputStream} object used to write data to the blob. * @@ -468,7 +600,7 @@ public BlobOutputStream openWriteExisting() throws StorageException { /** * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and - * operation context. The page blob must already exist. + * operation context. The page blob must already exist and any existing data may be overwritten. * * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the blob. @@ -495,7 +627,11 @@ public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobR /** * Opens an output stream object to write data to the page blob. The page blob does not need to yet exist and will - * be created with the length specified. + * be created with the length specified. If the blob already exists on the service, it will be overwritten. + *

+ * To avoid overwriting and instead throw an error, please use the + * {@link #openWriteNew(long, AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate + * {@link AccessCondition}. * * @param length * A long which represents the length, in bytes, of the stream to create. This value must be @@ -514,7 +650,11 @@ public BlobOutputStream openWriteNew(final long length) throws StorageException /** * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and - * operation context. The page blob does not need to yet exist and will be created with the length specified. + * operation context. The page blob does not need to yet exist and will be created with the length specified.If the + * blob already exists on the service, it will be overwritten. + *

+ * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. * * @param length * A long which represents the length, in bytes, of the stream to create. This value must be @@ -574,7 +714,7 @@ private BlobOutputStream openOutputStreamInternal(Long length, AccessCondition a assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient, + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient, false /* setStartTime */); if (options.getStoreBlobContentMD5()) { @@ -667,10 +807,10 @@ public void setHeaders(HttpURLConnection connection, CloudPageBlob blob, Operati public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { if (operationType == PageOperationType.UPDATE) { - StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context); } else { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } } @@ -743,7 +883,7 @@ public void resize(long size, AccessCondition accessCondition, BlobRequestOption } opContext.initialize(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.resizeImpl(size, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -764,7 +904,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudPageBlob blob @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -786,10 +926,11 @@ public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, Opera } /** - * Uploads the source stream data to the page blob. + * Uploads the source stream data to the page blob. If the blob already exists on the service, it will be + * overwritten. * * @param sourceStream - * An {@link IntputStream} object to read from. + * An {@link InputStream} object to read from. * @param length * A long which represents the length, in bytes, of the stream data, must be non zero and a * multiple of 512. @@ -807,10 +948,10 @@ public void upload(final InputStream sourceStream, final long length) throws Sto /** * Uploads the source stream data to the page blob using the specified lease ID, request options, and operation - * context. + * context. If the blob already exists on the service, it will be overwritten. * * @param sourceStream - * An {@link IntputStream} object to read from. + * An {@link InputStream} object to read from. * @param length * A long which represents the length, in bytes, of the stream data. This must be great than * zero and a multiple of 512. @@ -840,7 +981,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); if (length <= 0 || length % Constants.PAGE_SIZE != 0) { throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH); @@ -868,7 +1009,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce * Uploads a range of contiguous pages, up to 4 MB in size, at the specified offset in the page blob. * * @param sourceStream - * An {@link IntputStream} object which represents the input stream to write to the page blob. + * An {@link InputStream} object which represents the input stream to write to the page blob. * @param offset * A long which represents the offset, in number of bytes, at which to begin writing the * data. This value must be a multiple of 512. @@ -894,7 +1035,7 @@ public void uploadPages(final InputStream sourceStream, final long offset, final * specified lease ID, request options, and operation context. * * @param sourceStream - * An {@link IntputStream} object which represents the input stream to write to the page blob. + * An {@link InputStream} object which represents the input stream to write to the page blob. * @param offset * A long which represents the offset, in number of bytes, at which to begin writing the * data. This value must be a multiple of @@ -943,7 +1084,7 @@ public void uploadPages(final InputStream sourceStream, final long offset, final opContext = new OperationContext(); } - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); final PageRange pageRange = new PageRange(offset, offset + length - 1); final byte[] data = new byte[(int) length]; @@ -975,7 +1116,7 @@ public void uploadPages(final InputStream sourceStream, final long offset, final /** * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream}. * - * @param pageBlobStreamWriteSizeInBytes + * @param streamWriteSizeInBytes * An int which represents the maximum number of bytes to buffer when writing to a page blob * stream. This value must be a * multiple of 512 and diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyState.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyState.java index 87d9c9ba78598..99210e226068e 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyState.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyState.java @@ -22,7 +22,7 @@ */ public final class CopyState { /** - * Holds the name of the container. + * Holds the ID for the copy operation. */ private String copyId; @@ -58,15 +58,9 @@ public final class CopyState { private String statusDescription; /** - * Initializes a new instance of the CopyState class. - */ - public CopyState() { - } - - /** - * Gets the copy ID of the container. + * Gets the ID of the copy operation. * - * @return A String which represents the copy ID of the container. + * @return A String which represents the ID of the copy operation. */ public String getCopyId() { return this.copyId; @@ -93,7 +87,7 @@ public CopyStatus getStatus() { /** * Gets the source URI of the copy operation. * - * @return A {@link java.net.URI} objeect which represents the source URI of the copy operation in a string. + * @return A {@link java.net.URI} object which represents the source URI of the copy operation in a string. */ public URI getSource() { return this.source; @@ -111,7 +105,7 @@ public Long getBytesCopied() { /** * Gets the number of bytes total number of bytes to copy. * - * @returnA long which represents the total number of bytes to copy/ + * @return A long which represents the total number of bytes to copy/ */ public Long getTotalBytes() { return this.totalBytes; @@ -127,13 +121,13 @@ public String getStatusDescription() { } /** - * Sets the copy ID of the container. + * Sets the ID of the copy operation. * * @param copyId - * A String which specifies the copy ID of the container to set. + * A String which specifies the ID of the copy operation to set. * */ - protected void setCopyId(final String copyId) { + void setCopyId(final String copyId) { this.copyId = copyId; } @@ -143,7 +137,7 @@ protected void setCopyId(final String copyId) { * @param completionTime * A {@link java.util.Date} object which specifies the time when the copy operation completed. */ - protected void setCompletionTime(final Date completionTime) { + void setCompletionTime(final Date completionTime) { this.completionTime = completionTime; } @@ -153,7 +147,7 @@ protected void setCompletionTime(final Date completionTime) { * @param status * A {@link CopyStatus} object specifies the status of the copy operation. */ - protected void setStatus(final CopyStatus status) { + void setStatus(final CopyStatus status) { this.status = status; } @@ -163,7 +157,7 @@ protected void setStatus(final CopyStatus status) { * @param source * A {@link java.net.URI} object which specifies the source URI. */ - protected void setSource(final URI source) { + void setSource(final URI source) { this.source = source; } @@ -173,7 +167,7 @@ protected void setSource(final URI source) { * @param bytesCopied * A long which specifies the number of bytes copied. */ - protected void setBytesCopied(final Long bytesCopied) { + void setBytesCopied(final Long bytesCopied) { this.bytesCopied = bytesCopied; } @@ -183,7 +177,7 @@ protected void setBytesCopied(final Long bytesCopied) { * @param totalBytes * A long which specifies the number of bytes to copy. */ - protected void setTotalBytes(final Long totalBytes) { + void setTotalBytes(final Long totalBytes) { this.totalBytes = totalBytes; } @@ -193,7 +187,6 @@ protected void setTotalBytes(final Long totalBytes) { * @param statusDescription * A String which specifies the status description. */ - protected void setStatusDescription(final String statusDescription) { + void setStatusDescription(final String statusDescription) { this.statusDescription = statusDescription; - } -} + }} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyStatus.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyStatus.java index dc29a799f6660..6ab1b6cc7e89a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyStatus.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CopyStatus.java @@ -60,7 +60,7 @@ public enum CopyStatus { * * @return A CopyStatus value that represents the copy status. */ - protected static CopyStatus parse(final String typeString) { + static CopyStatus parse(final String typeString) { if (Utility.isNullOrEmpty(typeString)) { return UNSPECIFIED; } @@ -83,4 +83,4 @@ else if ("failed".equals(typeString.toLowerCase(Locale.US))) { return UNSPECIFIED; } } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobHeaders.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobHeaders.java index 134ef5caadc2f..a23ab851003f1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobHeaders.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobHeaders.java @@ -14,153 +14,25 @@ */ package com.microsoft.azure.storage.blob; -import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.SharedAccessHeaders; /** * Represents the optional headers that can be returned with blobs accessed using SAS. */ -public final class SharedAccessBlobHeaders { - - /** - * The cache-control header returned with the blob. - */ - private String cacheControl; - - /** - * The content-disposition header returned with the blob. - */ - private String contentDisposition; - - /** - * The content-encoding header returned with the blob. - */ - private String contentEncoding; - - /** - * The content-language header returned with the blob. - */ - private String contentLanguage; - - /** - * The content-type header returned with the blob. - */ - private String contentType; - +public final class SharedAccessBlobHeaders extends SharedAccessHeaders { /** * Initializes a new instance of the {@link SharedAccessBlobHeaders} class. */ public SharedAccessBlobHeaders() { - } /** * Initializes a new instance of the {@link SharedAccessBlobHeaders} class based on an existing instance. * * @param other - * A {@link SharedAccessBlobHeaders} object which specifies the set of blob properties to clone. - */ - public SharedAccessBlobHeaders(SharedAccessBlobHeaders other) { - Utility.assertNotNull("other", other); - - this.contentType = other.contentType; - this.contentDisposition = other.contentDisposition; - this.contentEncoding = other.contentEncoding; - this.contentLanguage = other.contentLanguage; - this.cacheControl = other.cacheControl; - } - - /** - * Gets the cache control header. - * - * @return A String which represents the cache control header. - */ - public final String getCacheControl() { - return this.cacheControl; - } - - /** - * Sets the cache control header. - * - * @param cacheControl - * A String which specifies the cache control header. - */ - public void setCacheControl(String cacheControl) { - this.cacheControl = cacheControl; - } - - /** - * Gets the content disposition header. - * - * @return A String which represents the content disposition header. - */ - public final String getContentDisposition() { - return this.contentDisposition; - } - - /** - * Sets the content disposition header. - * - * @param contentDisposition - * A String which specifies the content disposition header. - */ - public void setContentDisposition(String contentDisposition) { - this.contentDisposition = contentDisposition; - } - - /** - * Gets the content encoding header. - * - * @return A String which represents the content encoding header. - */ - public final String getContentEncoding() { - return this.contentEncoding; - } - - /** - * Sets the content encoding header. - * - * @param contentEncoding - * A String which specifies the content encoding header. - */ - public void setContentEncoding(String contentEncoding) { - this.contentEncoding = contentEncoding; - } - - /** - * Gets the content language header. - * - * @return A String which represents the content language header. - */ - public final String getContentLanguage() { - return this.contentLanguage; - } - - /** - * Sets the content language header. - * - * @param contentLanguage - * A String which specifies the content language header. - */ - public void setContentLanguage(String contentLanguage) { - this.contentLanguage = contentLanguage; - } - - /** - * Gets the content type header. - * - * @return A String which represents the content type header. - */ - public final String getContentType() { - return this.contentType; - } - - /** - * Sets the content type header. - * - * @param contentType - * A String which specifies the content type header. + * A {@link SharedAccessHeaders} object which specifies the set of properties to clone. */ - public void setContentType(String contentType) { - this.contentType = contentType; + public SharedAccessBlobHeaders(SharedAccessHeaders other) { + super(other); } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPermissions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPermissions.java index 61e3da2a3158b..d9d46a05ee937 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPermissions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPermissions.java @@ -14,8 +14,6 @@ */ package com.microsoft.azure.storage.blob; -import java.util.EnumSet; - /** * Specifies the set of possible permissions for a shared access policy. */ @@ -23,63 +21,20 @@ public enum SharedAccessBlobPermissions { /** * Specifies Read access granted. */ - READ((byte) 0x1), + READ, /** * Specifies Write access granted. */ - WRITE((byte) 0x2), + WRITE, /** * Specifies Delete access granted for blobs. */ - DELETE((byte) 0x4), + DELETE, /** * Specifies List access granted. */ - LIST((byte) 0x8); - - /** - * Returns the enum set representing the shared access permissions for the specified byte value. - * - * @param value - * The byte value to convert to the corresponding enum set. - * @return A java.util.EnumSet object that contains the SharedAccessBlobPermissions values - * corresponding to the specified byte value. - */ - protected static EnumSet fromByte(final byte value) { - final EnumSet retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); - - if (value == READ.value) { - retSet.add(READ); - } - - if (value == WRITE.value) { - retSet.add(WRITE); - } - if (value == DELETE.value) { - retSet.add(DELETE); - } - if (value == LIST.value) { - retSet.add(LIST); - } - - return retSet; - } - - /** - * Represents the value of this enum. - */ - private byte value; - - /** - * Sets the value of this enum. - * - * @param val - * A byte which specifies the value being assigned. - */ - private SharedAccessBlobPermissions(final byte val) { - this.value = val; - } -} + LIST; +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPolicy.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPolicy.java index 10d2b305ce988..d1ecfa0c85454 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPolicy.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/SharedAccessBlobPolicy.java @@ -18,6 +18,7 @@ import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.SharedAccessPolicy; +import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions; /** * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access @@ -28,7 +29,7 @@ public final class SharedAccessBlobPolicy extends SharedAccessPolicy { * The permissions for a shared access signature associated with this shared access policy. */ private EnumSet permissions; - + /** * Gets the permissions for a shared access signature associated with this shared access policy. * @@ -49,12 +50,12 @@ public EnumSet getPermissions() { public void setPermissions(final EnumSet permissions) { this.permissions = permissions; } - + /** * Converts this policy's permissions to a string. * - * @return A String that represents the shared access permissions in the "rwdl" format, which is - * described at {@link SharedAccessBlobPolicy#permissionsFromString(String)}. + * @return A String that represents the shared access permissions in the "rwdl" format, + * which is described at {@link #setPermissionsFromString(String)}. */ @Override public String permissionsToString() { @@ -98,30 +99,27 @@ public String permissionsToString() { *

  • w: Write access.
  • * */ - @Override public void setPermissionsFromString(final String value) { - final char[] chars = value.toCharArray(); - final EnumSet retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); - - for (final char c : chars) { + EnumSet initial = EnumSet.noneOf(SharedAccessBlobPermissions.class); + for (final char c : value.toCharArray()) { switch (c) { case 'r': - retSet.add(SharedAccessBlobPermissions.READ); + initial.add(SharedAccessBlobPermissions.READ); break; case 'w': - retSet.add(SharedAccessBlobPermissions.WRITE); + initial.add(SharedAccessBlobPermissions.WRITE); break; case 'd': - retSet.add(SharedAccessBlobPermissions.DELETE); + initial.add(SharedAccessBlobPermissions.DELETE); break; case 'l': - retSet.add(SharedAccessBlobPermissions.LIST); + initial.add(SharedAccessBlobPermissions.LIST); break; default: throw new IllegalArgumentException("value"); } } - - this.permissions = retSet; + + this.permissions = initial; } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Base64.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Base64.java index 33957346bfddf..8dededf3ffd2a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Base64.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Base64.java @@ -59,6 +59,10 @@ public final class Base64 { * If the string is not a valid base64 encoded string */ public static byte[] decode(final String data) { + if (data == null) { + throw new IllegalArgumentException(SR.STRING_NOT_VALID); + } + int byteArrayLength = 3 * data.length() / 4; if (data.endsWith("==")) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java index d2373670eb7ca..51d98d9c6c2aa 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java @@ -20,19 +20,20 @@ import java.net.URISyntaxException; import java.net.URL; import java.security.InvalidKeyException; -import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.Credentials; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RequestOptions; +import com.microsoft.azure.storage.StorageCredentialsAccountAndKey; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageKey; /** * RESERVED FOR INTERNAL USE. The Base Request class for the protocol layer. */ +@SuppressWarnings("deprecation") public final class BaseRequest { private static final String METADATA = "metadata"; @@ -56,7 +57,7 @@ public final class BaseRequest { * @param metadata * The metadata. */ - public static void addMetadata(final HttpURLConnection request, final HashMap metadata, + public static void addMetadata(final HttpURLConnection request, final Map metadata, final OperationContext opContext) { if (metadata != null) { for (final Entry entry : metadata.entrySet()) { @@ -166,17 +167,25 @@ public static HttpURLConnection createURLConnection(final URI uri, final Request builder = new UriQueryBuilder(); } - final URL resourceUrl = builder.addToURI(uri).toURL(); - - final HttpURLConnection retConnection = (HttpURLConnection) resourceUrl.openConnection(); - if (options.getTimeoutIntervalInMs() != null && options.getTimeoutIntervalInMs() != 0) { builder.add(TIMEOUT, String.valueOf(options.getTimeoutIntervalInMs() / 1000)); } + + final URL resourceUrl = builder.addToURI(uri).toURL(); + + final HttpURLConnection retConnection = (HttpURLConnection) resourceUrl.openConnection(); - // Note: ReadTimeout must be explicitly set to avoid a bug in JDK 6. - // In certain cases, this bug causes an immediate read timeout exception to be thrown even if ReadTimeout is not set. - retConnection.setReadTimeout(Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs(), options.getTimeoutIntervalInMs())); + /* + * ReadTimeout must be explicitly set to avoid a bug in JDK 6. In certain cases, this bug causes an immediate + * read timeout exception to be thrown even if ReadTimeout is not set. + * + * Both connect and read timeout are set to the same value as we have no way of knowing how to partition + * the remaining time between these operations. The user can override these timeouts using the SendingRequest + * event handler if more control is desired. + */ + int timeout = Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs(), options.getTimeoutIntervalInMs()); + retConnection.setReadTimeout(timeout); + retConnection.setConnectTimeout(timeout); // Note : accept behavior, java by default sends Accept behavior as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, Constants.HeaderConstants.XML_TYPE); @@ -446,58 +455,23 @@ public static HttpURLConnection setServiceProperties(final URI uri, final Reques * if the credentials key is invalid. * @throws StorageException */ - public static void signRequestForBlobAndQueue(final HttpURLConnection request, final Credentials credentials, - final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException { + public static void signRequestForBlobAndQueue(final HttpURLConnection request, + final StorageCredentialsAccountAndKey credentials, final Long contentLength, + final OperationContext opContext) throws InvalidKeyException, StorageException { request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime()); final Canonicalizer canonicalizer = CanonicalizerFactory.getBlobQueueFullCanonicalizer(request); final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength); - final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign); + final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getCredentials().getKey(), + stringToSign); - // V2 add logging - // System.out.println(String.format("Signing %s\r\n%s\r\n", stringToSign, computedBase64Signature)); + Logger.trace(opContext, LogConstants.SIGNING, stringToSign); + request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, String.format("%s %s:%s", "SharedKey", credentials.getAccountName(), computedBase64Signature)); } - /** - * - * Signs the request appropriately to make it an authenticated request for Blob and Queue. - * - * @param request - * a HttpURLConnection for the operation. - * @param credentials - * the credentials to use for signing. - * @param contentLength - * the length of the content written to the output stream, -1 if unknown. - * @param opContext - * an object used to track the execution of the operation - * @throws InvalidKeyException - * if the credentials key is invalid. - * @throws StorageException - * - * @deprecated as of 2.0.0. Use {@link #signRequestForBlobAndQueue} instead. - */ - @Deprecated - public static void signRequestForBlobAndQueueSharedKeyLite(final HttpURLConnection request, - final Credentials credentials, final Long contentLength, final OperationContext opContext) - throws InvalidKeyException, StorageException { - request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime()); - - final Canonicalizer canonicalizer = CanonicalizerFactory.getBlobQueueLiteCanonicalizer(request); - - final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength); - - final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign); - - // VNext add logging - // System.out.println(String.format("Signing %s\r\n%s\r\n", - // stringToSign, computedBase64Signature)); - request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, - String.format("%s %s:%s", "SharedKeyLite", credentials.getAccountName(), computedBase64Signature)); - } - /** * * Signs the request appropriately to make it an authenticated request for Table. @@ -514,55 +488,26 @@ public static void signRequestForBlobAndQueueSharedKeyLite(final HttpURLConnecti * if the credentials key is invalid. * @throws StorageException */ - public static void signRequestForTableSharedKey(final HttpURLConnection request, final Credentials credentials, - final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException { + public static void signRequestForTableSharedKey(final HttpURLConnection request, + final StorageCredentialsAccountAndKey credentials, final Long contentLength, + final OperationContext opContext) throws InvalidKeyException, StorageException { request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime()); final Canonicalizer canonicalizer = CanonicalizerFactory.getTableFullCanonicalizer(request); final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength); - final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign); + final String computedBase64Signature = StorageKey.computeMacSha256( + credentials.getCredentials().getKey(), stringToSign); + + Logger.trace(opContext, LogConstants.SIGNING, stringToSign); request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, String.format("%s %s:%s", "SharedKey", credentials.getAccountName(), computedBase64Signature)); } /** - * - * Signs the request appropriately to make it an authenticated request for Table. - * - * @param request - * a HttpURLConnection for the operation. - * @param credentials - * the credentials to use for signing. - * @param contentLength - * the length of the content written to the output stream, -1 if unknown. - * @param opContext - * an object used to track the execution of the operation - * @throws InvalidKeyException - * if the credentials key is invalid. - * @throws StorageException - * - * @deprecated as of 2.0.0. Use {@link #signRequestForTableSharedKey} instead. - */ - @Deprecated - public static void signRequestForTableSharedKeyLite(final HttpURLConnection request, final Credentials credentials, - final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException { - request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime()); - - final Canonicalizer canonicalizer = CanonicalizerFactory.getTableLiteCanonicalizer(request); - - final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength); - - final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign); - - request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, - String.format("%s %s:%s", "SharedKeyLite", credentials.getAccountName(), computedBase64Signature)); - } - - /** - * Private Default Ctor + * A private default constructor. All methods of this class are static so no instances of it should ever be created. */ private BaseRequest() { // No op diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueFullCanonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueFullCanonicalizer.java index 10d3053f4deb8..9091b44da57bb 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueFullCanonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueFullCanonicalizer.java @@ -21,8 +21,8 @@ import com.microsoft.azure.storage.StorageException; /** - * RESERVED FOR INTERNAL USE. Provides an implementation of the Canonicalizer class for requests against Blob and Queue - * Service under the Shared Key authentication scheme. + * RESERVED FOR INTERNAL USE. Provides an implementation of the Canonicalizer class for requests against Blob, Queue, + * and File Service under the Shared Key authentication scheme. */ final class BlobQueueFullCanonicalizer extends Canonicalizer { @@ -39,7 +39,8 @@ final class BlobQueueFullCanonicalizer extends Canonicalizer { * @throws StorageException */ @Override - protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) throws StorageException { + protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) + throws StorageException { if (contentLength < -1) { throw new InvalidParameterException(SR.INVALID_CONTENT_LENGTH); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueLiteCanonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueLiteCanonicalizer.java index e87258a583b83..1e757d51bc20a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueLiteCanonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BlobQueueLiteCanonicalizer.java @@ -21,8 +21,8 @@ import com.microsoft.azure.storage.StorageException; /** - * RESERVED FOR INTERNAL USE. Provides an implementation of the Canonicalizer class for requests against Blob and Queue - * Service under the Shared Key authentication scheme. + * RESERVED FOR INTERNAL USE. Provides an implementation of the Canonicalizer class for requests against Blob, Queue, + * and File Service under the Shared Key authentication scheme. */ final class BlobQueueLiteCanonicalizer extends Canonicalizer { @@ -39,7 +39,8 @@ final class BlobQueueLiteCanonicalizer extends Canonicalizer { * @throws StorageException */ @Override - protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) throws StorageException { + protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) + throws StorageException { if (contentLength < -1) { throw new InvalidParameterException(SR.INVALID_CONTENT_LENGTH); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java index 9aca38ad60927..f8f713d00969c 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java @@ -156,7 +156,7 @@ protected static String canonicalizeHttpRequest(final java.net.URL address, fina appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_LANGUAGE)); appendCanonicalizedElement(canonicalizedString, - contentLength == -1 ? Constants.EMPTY_STRING : String.valueOf(contentLength)); + contentLength <= 0 ? Constants.EMPTY_STRING : String.valueOf(contentLength)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_MD5)); appendCanonicalizedElement(canonicalizedString, contentType != null ? contentType : Constants.EMPTY_STRING); @@ -330,8 +330,8 @@ protected static String getCanonicalizedResource(final java.net.URL address, fin } // key turns out to be null for ?a&b&c&d - lowercasedKeyNameValue.put(entry.getKey() == null ? null : entry.getKey().toLowerCase(Utility.LOCALE_US), - stringValue.toString()); + lowercasedKeyNameValue.put((entry.getKey()) == null ? null : + entry.getKey().toLowerCase(Utility.LOCALE_US), stringValue.toString()); } final ArrayList sortedKeys = new ArrayList(lowercasedKeyNameValue.keySet()); @@ -405,7 +405,7 @@ protected static String getCanonicalizedResourceLite(final java.net.URL address, * a one to many map of key / values representing the header values for the connection. * @param headerName * the name of the header to lookup - * @return an ArrayList of all trimmed values cooresponding to the requested headerName. This may be empty + * @return an ArrayList of all trimmed values corresponding to the requested headerName. This may be empty * if the header is not found. */ private static ArrayList getHeaderValues(final Map> headers, final String headerName) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/CanonicalizerFactory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/CanonicalizerFactory.java index b5ebc068ed9a0..8ce15a0500650 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/CanonicalizerFactory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/CanonicalizerFactory.java @@ -90,7 +90,7 @@ protected static Canonicalizer getTableLiteCanonicalizer(final HttpURLConnection } /** - * Private Default Ctor + * A private default constructor. All methods of this class are static so no instances of it should ever be created. */ private CanonicalizerFactory() { // No op diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java index adedd1c55dff0..976fe847eab8f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java @@ -18,16 +18,10 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.URISyntaxException; -import java.security.InvalidKeyException; import java.util.Date; import java.util.Map.Entry; import java.util.concurrent.TimeoutException; -import javax.xml.stream.XMLStreamException; - import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; @@ -44,7 +38,6 @@ import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageLocation; -import com.microsoft.azure.storage.table.TableServiceException; /** * RESERVED FOR INTERNAL USE. A class that handles execution of StorageOperations and enforces retry policies. @@ -190,95 +183,21 @@ public static RESULT_TYPE executeWithRet } } } - catch (final TimeoutException e) { - // Retryable - Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = StorageException.translateException(task, e, opContext); - task.getResult().setException(translatedException); - } - catch (final SocketTimeoutException e) { - // Retryable - Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = new StorageException(StorageErrorCodeStrings.OPERATION_TIMED_OUT, - "The operation did not complete in the specified time.", -1, null, e); - task.getResult().setException(translatedException); - } - catch (final IOException e) { - // Non Retryable if the inner exception is actually an TimeoutException, otherwise Retryable - if (e.getCause() instanceof TimeoutException) { - translatedException = new StorageException(StorageErrorCodeStrings.OPERATION_TIMED_OUT, - SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, Constants.HeaderConstants.HTTP_UNUSED_306, null, - (Exception) e.getCause()); - task.getResult().setException(translatedException); - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getCause().getClass().getName(), e - .getCause().getMessage()); - throw translatedException; - } - else { - Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = StorageException.translateException(task, e, opContext); - task.getResult().setException(translatedException); - } - } - catch (final XMLStreamException e) { - // Non Retryable except when the inner exception is actually an IOException - if (e.getCause() instanceof SocketException) { - translatedException = StorageException.translateException(task, - (Exception) e.getCause(), opContext); - } - else { - translatedException = StorageException.translateException(task, e, opContext); - } - - task.getResult().setException(translatedException); - - if (!(e.getCause() instanceof IOException)) { - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw translatedException; - } - Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - } - catch (final InvalidKeyException e) { - // Non Retryable, just throw - translatedException = StorageException.translateException(task, e, opContext); - task.getResult().setException(translatedException); - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw translatedException; - } - catch (final URISyntaxException e) { - // Non Retryable, just throw - translatedException = StorageException.translateException(task, e, opContext); - task.getResult().setException(translatedException); - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw translatedException; - } - catch (final TableServiceException e) { + catch (final StorageException e) { + // In case of table batch error or internal error, the exception will contain a different + // status code and message than the original HTTP response. Reset based on error values. task.getResult().setStatusCode(e.getHttpStatusCode()); task.getResult().setStatusMessage(e.getMessage()); task.getResult().setException(e); - - if (!e.isRetryable()) { - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw e; - } - else { - Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = e; - } - } - catch (final StorageException e) { - // Non Retryable, just throw - // do not translate StorageException - task.getResult().setException(e); - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw e; + + Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); + translatedException = e; } catch (final Exception e) { - // Non Retryable, just throw + // Retryable, wrap + Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); - Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - throw translatedException; } finally { opContext.setClientTimeInMs(new Date().getTime() - startTime); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java index 8ce774dd1df7f..55fd5be15c245 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java @@ -35,6 +35,7 @@ public class LogConstants { public static final String RETRY_DELAY = "Operation will be retried after '%d'ms."; public static final String RETRY_INFO = "The retry policy set the next location to '%s' and updated the location mode to '%s'."; public static final String RETRYABLE_EXCEPTION = "Retryable exception thrown. Class = '%s', Message = '%s'."; + public static final String SIGNING = "Signing %s"; public static final String START_REQUEST = "Starting request to '%s' at '%s'."; public static final String STARTING = "Starting operation."; public static final String UNEXPECTED_RESULT_OR_EXCEPTION = "Operation did not return the expected result or returned an exception."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/PathUtility.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/PathUtility.java index 1e846c0358752..03449276091b2 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/PathUtility.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/PathUtility.java @@ -234,28 +234,59 @@ public static String getContainerNameFromUri(final URI resourceAddress, final bo /** * Gets the file name from the URI. * - * @param inURI - * the resource address + * @param resourceAddress + * the file URI * @param usePathStyleUris - * a value indicating if the address is a path style uri. + * a value indicating if the address is a path style URI * @return the file's name */ - public static String getFileNameFromURI(final URI inURI, final boolean usePathStyleUris) { - final String[] pathSegments = inURI.getRawPath().split("/"); + public static String getFileNameFromURI(final URI resourceAddress, final boolean usePathStyleUris) { + // generate an array of the different levels of the path + final String[] pathSegments = resourceAddress.getRawPath().split("/"); + // usePathStyleUris ? baseuri/accountname/sharename/objectname : accountname.baseuri/sharename/objectname + final int shareIndex = usePathStyleUris ? 2 : 1; + + if (pathSegments.length - 1 <= shareIndex) { + // legal file addresses cannot end with or before the sharename + throw new IllegalArgumentException(String.format("Invalid file address '%s'.", resourceAddress)); + } + else { + // in a legal file address the lowest level is the filename + return pathSegments[pathSegments.length - 1]; + } + } + + /** + * Get the name of the lowest level directory from the given directory address. + * + * @param resourceAddress + * the directory URI + * @param usePathStyleUris + * a value indicating if the address is a path style URI + * @return directory name from address from the URI + */ + public static String getDirectoryNameFromURI(final URI resourceAddress, final boolean usePathStyleUris) { + // generate an array of the different levels of the path + final String[] pathSegments = resourceAddress.getRawPath().split("/"); + + // usePathStyleUris ? baseuri/accountname/sharename/objectname : accountname.baseuri/sharename/objectname final int shareIndex = usePathStyleUris ? 2 : 1; if (pathSegments.length - 1 < shareIndex) { - throw new IllegalArgumentException(String.format("Invalid file address '%s'.", inURI)); + // if the sharename is missing or too close to the end + throw new IllegalArgumentException(String.format("Invalid directory address '%s'.", resourceAddress)); } else if (pathSegments.length - 1 == shareIndex) { + // this is the root directory; it has no name return ""; } else { + // in a legal directory address the lowest level is the directory return pathSegments[pathSegments.length - 1]; } } - + /** * Get the share name from address from the URI. * @@ -402,6 +433,25 @@ public static String getServiceClientBaseAddress(final URI address, final boolea } } + /** + * Get the service client address from a complete Uri. + * + * @param address + * Complete address of the resource. + * @param usePathStyleUris + * a value indicating if the address is a path style uri. + * @return the service client address from a complete Uri. + * @throws StorageException + */ + public static StorageUri getServiceClientBaseAddress(final StorageUri addressUri) throws StorageException { + boolean usePathStyleUris = Utility.determinePathStyleFromUri(addressUri.getPrimaryUri()); + try { + return getServiceClientBaseAddress(addressUri, usePathStyleUris); + } catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); + } + } + /** * Get the service client address from a complete Uri. * @@ -415,8 +465,8 @@ public static String getServiceClientBaseAddress(final URI address, final boolea public static StorageUri getServiceClientBaseAddress(final StorageUri addressUri, final boolean usePathStyleUris) throws URISyntaxException { return new StorageUri(new URI(getServiceClientBaseAddress(addressUri.getPrimaryUri(), usePathStyleUris)), - addressUri.getSecondaryUri() != null ? new URI(getServiceClientBaseAddress( - addressUri.getSecondaryUri(), usePathStyleUris)) : null); + addressUri.getSecondaryUri() != null ? + new URI(getServiceClientBaseAddress(addressUri.getSecondaryUri(), usePathStyleUris)) : null); } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java index 58fc2e827d0c5..070e6b0e9dc69 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java @@ -20,6 +20,7 @@ */ public class SR { public static final String ACCOUNT_NAME_NULL_OR_EMPTY = "The account name is null or empty."; + public static final String APPEND_BLOB_MD5_NOT_POSSIBLE = "MD5 cannot be calculated for an existing append blob because it would require reading the existing data. Please disable StoreFileContentMD5."; public static final String ARGUMENT_NULL_OR_EMPTY = "The argument must not be null or an empty string. Argument name: %s."; public static final String ARGUMENT_OUT_OF_RANGE_ERROR = "The argument is out of range. Argument name: %s, Value passed: %s."; public static final String ATTEMPTED_TO_SERIALIZE_INACCESSIBLE_PROPERTY = "An attempt was made to access an inaccessible member of the entity during serialization."; @@ -67,6 +68,7 @@ public class SR { public static final String INVALID_ACL_ACCESS_TYPE = "Invalid acl public access type returned '%s'. Expected blob or container."; public static final String INVALID_BLOB_TYPE = "Incorrect Blob type, please use the correct Blob type to access a blob on the server. Expected %s, actual %s."; public static final String INVALID_BLOCK_ID = "Invalid blockID, blockID must be a valid Base64 String."; + public static final String INVALID_BLOCK_SIZE = "Append block data should not exceed the maximum blob size condition value."; public static final String INVALID_CONDITIONAL_HEADERS = "The conditionals specified for this operation did not match server."; public static final String INVALID_CONNECTION_STRING = "Invalid connection string."; public static final String INVALID_CONNECTION_STRING_DEV_STORE_NOT_TRUE = "Invalid connection string, the UseDevelopmentStorage key must always be paired with 'true'. Remove the flag entirely otherwise."; @@ -96,7 +98,6 @@ public class SR { public static final String INVALID_STORAGE_SERVICE = "Invalid storage service specified."; public static final String INVALID_STREAM_LENGTH = "Invalid stream length; stream must be between 0 and %s MB in length."; public static final String ITERATOR_EMPTY = "There are no more elements in this enumeration."; - public static final String KEY_NULL = "Key invalid. Cannot be null."; public static final String LEASE_CONDITION_ON_SOURCE = "A lease condition cannot be specified on the source of a copy."; public static final String LOG_STREAM_END_ERROR = "Error parsing log record: unexpected end of stream."; public static final String LOG_STREAM_DELIMITER_ERROR = "Error parsing log record: unexpected delimiter encountered."; @@ -111,6 +112,7 @@ public class SR { public static final String MISSING_MANDATORY_PARAMETER_FOR_SAS = "Missing mandatory parameters for valid Shared Access Signature."; public static final String MISSING_MD5 = "ContentMD5 header is missing in the response."; public static final String MISSING_NULLARY_CONSTRUCTOR = "Class type must contain contain a nullary constructor."; + public static final String MULTIPLE_CREDENTIALS_PROVIDED = "Cannot provide credentials as part of the address and as constructor parameter. Either pass in the address or use a different constructor."; public static final String OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY = "All entities in a given batch must have the same partition key."; public static final String PARAMETER_NOT_IN_RANGE = "The value of the parameter '%s' should be between %s and %s."; public static final String PARAMETER_SHOULD_BE_GREATER = "The value of the parameter '%s' should be greater than %s."; @@ -123,6 +125,7 @@ public class SR { public static final String PERMISSIONS_COULD_NOT_BE_PARSED = "Permissions could not be parsed from '%s'."; public static final String PRIMARY_ONLY_COMMAND = "This operation can only be executed against the primary storage location."; public static final String PROPERTY_CANNOT_BE_SERIALIZED_AS_GIVEN_EDMTYPE = "Property %s with Edm Type %s cannot be de-serialized."; + public static final String PRECONDITION_FAILURE_IGNORED = "Pre-condition failure on a retry is being ignored since the request should have succeeded in the first attempt."; public static final String QUERY_PARAMETER_NULL_OR_EMPTY = "Cannot encode a query parameter with a null or empty key."; public static final String QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER = "Query requires a valid class type or resolver."; public static final String QUEUE = "queue"; @@ -140,8 +143,7 @@ public class SR { public static final String SHARE = "share"; public static final String SNAPSHOT_LISTING_ERROR = "Listing snapshots is only supported in flat mode (no delimiter). Consider setting useFlatBlobListing to true."; public static final String SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED = "Snapshot query parameter is already defined in the blob URI. Either pass in a snapshotTime parameter or use a full URL with a snapshot query parameter."; - public static final String STORAGE_QUEUE_CREDENTIALS_NULL = "StorageCredentials cannot be null for the Queue service."; - public static final String STORAGE_TABLE_CREDENTIALS_NULL = "StorageCredentials cannot be null for the Table service."; + public static final String STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS = "StorageCredentials cannot be null or anonymous for this service."; public static final String STORAGE_CLIENT_OR_SAS_REQUIRED = "Either a SAS token or a service client must be specified."; public static final String STORAGE_URI_MISSING_LOCATION = "The URI for the target storage location is not specified. Please consider changing the request's location mode."; public static final String STORAGE_URI_MUST_MATCH = "Primary and secondary location URIs in a StorageUri must point to the same resource."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java index 3114820463041..aac0eea20bfed 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java @@ -15,18 +15,19 @@ package com.microsoft.azure.storage.core; import java.security.InvalidKeyException; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map.Entry; import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ServiceClient; +import com.microsoft.azure.storage.SharedAccessHeaders; import com.microsoft.azure.storage.SharedAccessPolicy; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageException; -import com.microsoft.azure.storage.blob.SharedAccessBlobHeaders; -import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; +import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.queue.SharedAccessQueuePolicy; import com.microsoft.azure.storage.table.SharedAccessTablePolicy; @@ -42,21 +43,22 @@ public class SharedAccessSignatureHelper { * @param groupPolicyIdentifier * An optional identifier for the policy. * @param resourceType - * Either "b" for blobs or "c" for containers. + * Either "b" for blobs, "c" for containers, "f" for files, or "s" for shares. * @param signature * The signature to use. * @return The finished query builder * @throws IllegalArgumentException * @throws StorageException */ - public static UriQueryBuilder generateSharedAccessSignatureForBlob(final SharedAccessBlobPolicy policy, - final SharedAccessBlobHeaders headers, final String groupPolicyIdentifier, final String resourceType, - final String signature) throws StorageException { + public static UriQueryBuilder generateSharedAccessSignatureForBlobAndFile( + final SharedAccessPolicy policy, final SharedAccessHeaders headers, + final String groupPolicyIdentifier, final String resourceType, final String signature) + throws StorageException { Utility.assertNotNullOrEmpty("resourceType", resourceType); return generateSharedAccessSignatureHelper(policy, null /* startPartitionKey */, null /* startRowKey */, null /* endPartitionKey */, null /* endRowKey */, groupPolicyIdentifier, resourceType, - null /* tableName */, signature, null /* accoutKetName */, headers); + null /* tableName */, signature, headers); } /** @@ -76,7 +78,7 @@ public static UriQueryBuilder generateSharedAccessSignatureForQueue(final Shared final String groupPolicyIdentifier, final String signature) throws StorageException { return generateSharedAccessSignatureHelper(policy, null /* startPartitionKey */, null /* startRowKey */, null /* endPartitionKey */, null /* endRowKey */, groupPolicyIdentifier, null /* resourceType */, - null /* tableName */, signature, null /* accountKeyName */, null /* headers */); + null /* tableName */, signature, null /* headers */); } /** @@ -85,37 +87,35 @@ public static UriQueryBuilder generateSharedAccessSignatureForQueue(final Shared public static UriQueryBuilder generateSharedAccessSignatureForTable(final SharedAccessTablePolicy policy, final String startPartitionKey, final String startRowKey, final String endPartitionKey, final String endRowKey, final String accessPolicyIdentifier, final String tableName, - final String signature, final String accountKeyName) throws StorageException { + final String signature) throws StorageException { Utility.assertNotNull("tableName", tableName); return generateSharedAccessSignatureHelper(policy, startPartitionKey, startRowKey, endPartitionKey, endRowKey, - accessPolicyIdentifier, null /* resourceType */, tableName, signature, accountKeyName, null /* headers */); + accessPolicyIdentifier, null /* resourceType */, tableName, signature, null /* headers */); } /** - * Get the signature hash embedded inside the Shared Access Signature for blob service. + * Get the signature hash embedded inside the Shared Access Signature for the blob or file service. * * @param policy * The shared access policy to hash. * @param headers - * The optional header values to set for a blob accessed with this shared access signature. + * The optional header values to set for a blob or file accessed with this shared access signature. * @param accessPolicyIdentifier * An optional identifier for the policy. * @param resourceName * The resource name. * @param client * The ServiceClient associated with the object. - * @param opContext - * An object used to track the execution of the operation + * * @return The signature hash embedded inside the Shared Access Signature. * @throws InvalidKeyException * @throws StorageException */ - public static String generateSharedAccessSignatureHashForBlob(final SharedAccessBlobPolicy policy, - final SharedAccessBlobHeaders headers, final String accessPolicyIdentifier, final String resourceName, - final ServiceClient client, final OperationContext opContext) throws InvalidKeyException, StorageException { - return generateSharedAccessSignatureHashForBlob(policy, resourceName, accessPolicyIdentifier, client, - opContext, headers); - + public static String generateSharedAccessSignatureHashForBlobAndFile(final SharedAccessPolicy policy, + final SharedAccessHeaders headers, final String accessPolicyIdentifier, final String resourceName, + final ServiceClient client) throws InvalidKeyException, StorageException { + return generateSharedAccessSignatureHashForBlobAndFile( + policy, resourceName, accessPolicyIdentifier, client, headers); } /** @@ -129,22 +129,21 @@ public static String generateSharedAccessSignatureHashForBlob(final SharedAccess * The resource name. * @param client * The ServiceClient associated with the object. - * @param opContext - * An object used to track the execution of the operation + * * @return The signature hash embedded inside the Shared Access Signature. * @throws InvalidKeyException * @throws StorageException */ public static String generateSharedAccessSignatureHashForQueue(final SharedAccessQueuePolicy policy, - final String accessPolicyIdentifier, final String resourceName, final ServiceClient client, - final OperationContext opContext) throws InvalidKeyException, StorageException { - return generateSharedAccessSignatureHashForQueueAndTable(policy, resourceName, accessPolicyIdentifier, false, - null, null, null, null, client, opContext); + final String accessPolicyIdentifier, final String resourceName, final ServiceClient client) + throws InvalidKeyException, StorageException { + return generateSharedAccessSignatureHashForQueueAndTable( + policy, resourceName, accessPolicyIdentifier, false, null, null, null, null, client); } /** - * Get the signature hash embedded inside the Shared Access Signature for blob service. + * Get the signature hash embedded inside the Shared Access Signature for the table service. * * @param policy * The shared access policy to hash. @@ -162,11 +161,26 @@ public static String generateSharedAccessSignatureHashForQueue(final SharedAcces */ public static String generateSharedAccessSignatureHashForTable(final SharedAccessTablePolicy policy, final String accessPolicyIdentifier, final String resourceName, final String startPartitionKey, - final String startRowKey, final String endPartitionKey, final String endRowKey, final ServiceClient client, - final OperationContext opContext) throws InvalidKeyException, StorageException { + final String startRowKey, final String endPartitionKey, final String endRowKey, final ServiceClient client) + throws InvalidKeyException, StorageException { return generateSharedAccessSignatureHashForQueueAndTable(policy, resourceName, accessPolicyIdentifier, true, - startPartitionKey, startRowKey, endPartitionKey, endRowKey, client, opContext); - + startPartitionKey, startRowKey, endPartitionKey, endRowKey, client); + } + + /** + * Parses the query parameters and populates a StorageCredentialsSharedAccessSignature object if one is present. + * + * @param completeUri + * A {@link StorageUri} object which represents the complete Uri. + * @return The StorageCredentialsSharedAccessSignature if one is present, otherwise null + * @throws IllegalArgumentException + * @throws StorageException + * An exception representing any error which occurred during the operation. + */ + public static StorageCredentialsSharedAccessSignature parseQuery(final StorageUri completeUri) + throws StorageException { + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + return parseQuery(queryParameters); } /** @@ -181,134 +195,47 @@ public static String generateSharedAccessSignatureHashForTable(final SharedAcces */ public static StorageCredentialsSharedAccessSignature parseQuery(final HashMap queryParams) throws StorageException { - String signature = null; - String signedStart = null; - String signedExpiry = null; - String signedResource = null; - String signedPermissions = null; - String signedIdentifier = null; - String signedVersion = null; - String cacheControl = null; - String contentType = null; - String contentEncoding = null; - String contentLanguage = null; - String contentDisposition = null; - String tableName = null; - String startPk = null; - String startRk = null; - String endPk = null; - String endRk = null; - boolean sasParameterFound = false; - - StorageCredentialsSharedAccessSignature credentials = null; - + List removeList = new ArrayList(); for (final Entry entry : queryParams.entrySet()) { final String lowerKey = entry.getKey().toLowerCase(Utility.LOCALE_US); - if (lowerKey.equals(Constants.QueryConstants.SIGNED_START)) { - signedStart = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNED_EXPIRY)) { - signedExpiry = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNED_PERMISSIONS)) { - signedPermissions = entry.getValue()[0]; + if (lowerKey.equals(Constants.QueryConstants.SIGNATURE)) { sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNED_RESOURCE)) { - signedResource = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNED_IDENTIFIER)) { - signedIdentifier = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNATURE)) { - signature = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SIGNED_VERSION)) { - signedVersion = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.CACHE_CONTROL)) { - cacheControl = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.CONTENT_TYPE)) { - contentType = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.CONTENT_ENCODING)) { - contentEncoding = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.CONTENT_LANGUAGE)) { - contentLanguage = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.CONTENT_DISPOSITION)) { - contentDisposition = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.SAS_TABLE_NAME)) { - tableName = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.START_PARTITION_KEY)) { - startPk = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.START_ROW_KEY)) { - startRk = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.END_PARTITION_KEY)) { - endPk = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(Constants.QueryConstants.END_ROW_KEY)) { - endRk = entry.getValue()[0]; - sasParameterFound = true; - } + } else if (lowerKey.equals(Constants.QueryConstants.COMPONENT)) { + removeList.add(entry.getKey()); + } else if (lowerKey.equals(Constants.QueryConstants.RESOURCETYPE)) { + removeList.add(entry.getKey()); + } else if (lowerKey.equals(Constants.QueryConstants.SNAPSHOT)) { + removeList.add(entry.getKey()); + } else if (lowerKey.equals(Constants.QueryConstants.API_VERSION)) { + removeList.add(entry.getKey()); + } + } + + for (String removeParam : removeList) { + queryParams.remove(removeParam); } if (sasParameterFound) { - if (signature == null) { - final String errorMessage = SR.MISSING_MANDATORY_PARAMETER_FOR_SAS; - throw new IllegalArgumentException(errorMessage); - } - final UriQueryBuilder builder = new UriQueryBuilder(); + + StringBuilder values = new StringBuilder(); + for (final Entry entry : queryParams.entrySet()) { + values.setLength(0); + for (int i = 0; i < entry.getValue().length; i++) { + values.append(entry.getValue()[i]); + values.append(','); + } + values.deleteCharAt(values.length() - 1); + + addIfNotNullOrEmpty(builder, entry.getKey().toLowerCase(), values.toString()); + } - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_START, signedStart); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_EXPIRY, signedExpiry); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_PERMISSIONS, signedPermissions); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_RESOURCE, signedResource); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_IDENTIFIER, signedIdentifier); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_VERSION, signedVersion); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNATURE, signature); - - addIfNotNullOrEmpty(builder, Constants.QueryConstants.CACHE_CONTROL, cacheControl); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.CONTENT_TYPE, contentType); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.CONTENT_ENCODING, contentEncoding); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.CONTENT_LANGUAGE, contentLanguage); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.CONTENT_DISPOSITION, contentDisposition); - - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SAS_TABLE_NAME, tableName); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.START_PARTITION_KEY, startPk); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.START_ROW_KEY, startRk); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.END_PARTITION_KEY, endPk); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.END_ROW_KEY, endRk); - - final String token = builder.toString(); - credentials = new StorageCredentialsSharedAccessSignature(token); + return new StorageCredentialsSharedAccessSignature(builder.toString()); } - return credentials; + return null; } /** @@ -348,23 +275,20 @@ private static void addIfNotNullOrEmpty(UriQueryBuilder builder, String name, St * @param accessPolicyIdentifier * An optional identifier for the policy. * @param resourceType - * Either "b" for blobs or "c" for containers. + * Either "b" for blobs, "c" for containers, "f" for files, or "s" for shares. * @param tableName * The table name. * @param signature * The signature hash. - * @param accountKeyName - * The account key name. * @param headers - * Optional blob headers. + * Optional blob or file headers. * @return The finished query builder * @throws StorageException */ private static UriQueryBuilder generateSharedAccessSignatureHelper(final SharedAccessPolicy policy, final String startPartitionKey, final String startRowKey, final String endPartitionKey, final String endRowKey, final String accessPolicyIdentifier, final String resourceType, - final String tableName, final String signature, final String accountKeyName, - final SharedAccessBlobHeaders headers) throws StorageException { + final String tableName, final String signature, final SharedAccessHeaders headers) throws StorageException { Utility.assertNotNull("signature", signature); String permissions = null; @@ -408,13 +332,12 @@ private static UriQueryBuilder generateSharedAccessSignatureHelper(final SharedA } addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNATURE, signature); - addIfNotNullOrEmpty(builder, Constants.QueryConstants.SIGNED_KEY, accountKeyName); return builder; } /** - * Get the signature hash embedded inside the Shared Access Signature for blob service. + * Get the signature hash embedded inside the Shared Access Signature for the blob or file service. * * @param permissions * The permissions for a shared access signature. @@ -428,18 +351,16 @@ private static UriQueryBuilder generateSharedAccessSignatureHelper(final SharedA * An optional identifier for the policy. * @param client * Reference to the ServiceClient. - * @param opContext - * An object used to track the execution of the operation. * @param headers - * The optional header values to set for a blob returned with this SAS. + * The optional header values to set for a blob or file returned with this SAS. * @return * The signature hash embedded inside the Shared Access Signature. * @throws InvalidKeyException * @throws StorageException */ - private static String generateSharedAccessSignatureHashForBlob(final SharedAccessPolicy policy, + private static String generateSharedAccessSignatureHashForBlobAndFile(final SharedAccessPolicy policy, final String resourceName, final String accessPolicyIdentifier, final ServiceClient client, - final OperationContext opContext, final SharedAccessBlobHeaders headers) throws InvalidKeyException, + final SharedAccessHeaders headers) throws InvalidKeyException, StorageException { Utility.assertNotNullOrEmpty("resourceName", resourceName); Utility.assertNotNull("client", client); @@ -479,10 +400,10 @@ private static String generateSharedAccessSignatureHashForBlob(final SharedAcces contentType == null ? Constants.EMPTY_STRING : contentType); stringToSign = Utility.safeDecode(stringToSign); - final String signature = StorageCredentialsHelper.computeHmac256(client.getCredentials(), stringToSign, - opContext); + final String signature = StorageCredentialsHelper.computeHmac256(client.getCredentials(), stringToSign); - // add logging + Logger.trace(null, LogConstants.SIGNING, stringToSign); + return signature; } @@ -501,8 +422,6 @@ private static String generateSharedAccessSignatureHashForBlob(final SharedAcces * An optional identifier for the policy. * @param client * Reference to the ServiceClient. - * @param opContext - * An object used to track the execution of the operation. * @return * the signature hash embedded inside the Shared Access Signature. * @throws InvalidKeyException @@ -511,7 +430,7 @@ private static String generateSharedAccessSignatureHashForBlob(final SharedAcces private static String generateSharedAccessSignatureHashForQueueAndTable(final SharedAccessPolicy policy, final String resourceName, final String accessPolicyIdentifier, final boolean useTableSas, final String startPartitionKey, final String startRowKey, final String endPartitionKey, - final String endRowKey, final ServiceClient client, final OperationContext opContext) + final String endRowKey, final ServiceClient client) throws InvalidKeyException, StorageException { Utility.assertNotNullOrEmpty("resourceName", resourceName); Utility.assertNotNull("client", client); @@ -540,10 +459,10 @@ private static String generateSharedAccessSignatureHashForQueueAndTable(final Sh } stringToSign = Utility.safeDecode(stringToSign); - final String signature = StorageCredentialsHelper.computeHmac256(client.getCredentials(), stringToSign, - opContext); + final String signature = StorageCredentialsHelper.computeHmac256(client.getCredentials(), stringToSign); - // add logging + Logger.trace(null, LogConstants.SIGNING, stringToSign); + return signature; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java index 4c6855974c009..4a3d9b9a88636 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java @@ -25,41 +25,18 @@ /** * RESERVED FOR INTERNAL USE. A helper method for StorageCredentials. */ -public class StorageCredentialsHelper { +@SuppressWarnings("deprecation") +public final class StorageCredentialsHelper { - // - // RESERVED, for internal use only. Gets a value indicating whether a - // request can be signed under the Shared Key authentication scheme using - // the specified credentials. - // - // @return True if a request can be signed with these - // credentials; otherwise, false - // - /** Reserved. */ - public static boolean canCredentialsSignRequest(final StorageCredentials creds) { - if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { - return true; - } - else { - return false; - } - } - - // - // RESERVED, for internal use only. Gets a value indicating whether a - // request can be signed under the Shared Key Lite authentication scheme - // using the specified credentials. - // - // @return true if a request can be signed with these - // credentials; otherwise, false - // - /** - * Reserved. - * - * @deprecated as of 2.0.0. Use {@link #canCredentialsSignRequest} instead. + /** + * RESERVED, for internal use only. Gets a value indicating whether a + * request can be signed under the Shared Key authentication scheme using + * the specified credentials. + + * @return true if a request can be signed with these + * credentials; otherwise, false */ - @Deprecated - public static boolean canCredentialsSignRequestLite(final StorageCredentials creds) { + public static boolean canCredentialsSignRequest(final StorageCredentials creds) { if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { return true; } @@ -80,27 +57,6 @@ public static boolean canCredentialsSignRequestLite(final StorageCredentials cre * If the key is not a valid Base64-encoded string. */ public static String computeHmac256(final StorageCredentials creds, final String value) throws InvalidKeyException { - return computeHmac256(creds, value, null); - } - - /** - * Computes a signature for the specified string using the HMAC-SHA256 algorithm with the specified operation - * context. - * - * @param value - * The UTF-8-encoded string to sign. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A String that contains the HMAC-SHA256-encoded signature. - * - * @throws InvalidKeyException - * If the key is not a valid Base64-encoded string. - */ - public static String computeHmac256(final StorageCredentials creds, final String value, - final OperationContext opContext) throws InvalidKeyException { if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { return StorageKey.computeMacSha256(((StorageCredentialsAccountAndKey) creds).getCredentials().getKey(), value); @@ -126,7 +82,7 @@ public static String computeHmac256(final StorageCredentials creds, final String public static void signBlobAndQueueRequest(final StorageCredentials creds, final java.net.HttpURLConnection request, final long contentLength) throws InvalidKeyException, StorageException { - signBlobAndQueueRequest(creds, request, contentLength, null); + signBlobQueueAndFileRequest(creds, request, contentLength, null); } /** @@ -146,65 +102,14 @@ public static void signBlobAndQueueRequest(final StorageCredentials creds, * @throws StorageException * If a storage service error occurred. */ - public static void signBlobAndQueueRequest(final StorageCredentials creds, + public static void signBlobQueueAndFileRequest(final StorageCredentials creds, final java.net.HttpURLConnection request, final long contentLength, OperationContext opContext) throws InvalidKeyException, StorageException { + if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { opContext = opContext == null ? new OperationContext() : opContext; - BaseRequest.signRequestForBlobAndQueue(request, ((StorageCredentialsAccountAndKey) creds).getCredentials(), - contentLength, opContext); - } - } - - /** - * Signs a request using the Shared Key Lite authentication scheme. - * - * @param request - * An HttpURLConnection object that represents the request to sign. - * @param contentLength - * The length of the content written to the output stream. If unknown, specify -1. - * - * @throws InvalidKeyException - * If the given key is invalid. - * @throws StorageException - * If an unspecified storage exception occurs. - * - * @deprecated as of 2.0.0. Use {@link #signBlobAndQueueRequest} instead. - */ - @Deprecated - public static void signBlobAndQueueRequestLite(final StorageCredentials creds, - final java.net.HttpURLConnection request, final long contentLength) throws InvalidKeyException, - StorageException { - signBlobAndQueueRequestLite(creds, request, contentLength, null); - } - - /** - * Signs a request using the specified operation context under the Shared Key Lite authentication scheme. - * - * @param request - * An HttpURLConnection object that represents the request to sign. - * @param contentLength - * The length of the content written to the output stream. If unknown, specify -1. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @throws InvalidKeyException - * If the given key is invalid. - * @throws StorageException - * If a storage service error occurred. - * - * @deprecated as of 2.0.0. Use {@link #signBlobAndQueueRequest} instead. - */ - @Deprecated - public static void signBlobAndQueueRequestLite(final StorageCredentials creds, - final java.net.HttpURLConnection request, final long contentLength, OperationContext opContext) - throws StorageException, InvalidKeyException { - if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { - opContext = opContext == null ? new OperationContext() : opContext; - BaseRequest.signRequestForBlobAndQueueSharedKeyLite(request, - ((StorageCredentialsAccountAndKey) creds).getCredentials(), contentLength, opContext); + BaseRequest.signRequestForBlobAndQueue( + request, (StorageCredentialsAccountAndKey) creds, contentLength, opContext); } } @@ -248,57 +153,14 @@ public static void signTableRequest(final StorageCredentials creds, final java.n if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { opContext = opContext == null ? new OperationContext() : opContext; BaseRequest.signRequestForTableSharedKey(request, - ((StorageCredentialsAccountAndKey) creds).getCredentials(), contentLength, opContext); + (StorageCredentialsAccountAndKey) creds, contentLength, opContext); } } - + /** - * Signs a request using the Shared Key Lite authentication scheme. - * - * @param request - * An HttpURLConnection object that represents the request to sign. - * @param contentLength - * The length of the content written to the output stream. If unknown, specify -1. - * - * @throws InvalidKeyException - * If the given key is invalid. - * @throws StorageException - * If an unspecified storage exception occurs. - * - * @deprecated as of 2.0.0. Use {@link #signTableRequest} instead. - */ - @Deprecated - public static void signTableRequestLite(final StorageCredentials creds, final java.net.HttpURLConnection request, - final long contentLength) throws InvalidKeyException, StorageException { - signTableRequestLite(creds, request, contentLength, null); - } - - /** - * Signs a request using the specified operation context under the Shared Key Lite authentication scheme. - * - * @param request - * An HttpURLConnection object that represents the request to sign. - * @param contentLength - * The length of the content written to the output stream. If unknown, specify -1. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @throws InvalidKeyException - * If the given key is invalid. - * @throws StorageException - * If a storage service error occurred. - * - * @deprecated as of 2.0.0. Use {@link #signTableRequest} instead. + * A private default constructor. All methods of this class are static so no instances of it should ever be created. */ - @Deprecated - public static void signTableRequestLite(final StorageCredentials creds, final java.net.HttpURLConnection request, - final long contentLength, OperationContext opContext) throws StorageException, InvalidKeyException { - if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { - opContext = opContext == null ? new OperationContext() : opContext; - BaseRequest.signRequestForTableSharedKeyLite(request, - ((StorageCredentialsAccountAndKey) creds).getCredentials(), contentLength, opContext); - } + private StorageCredentialsHelper() { + //No op } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageRequest.java index 7d94f451e77bc..09d6ff42e3fb3 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageRequest.java @@ -21,7 +21,6 @@ import java.security.InvalidKeyException; import com.microsoft.azure.storage.AccessCondition; -import com.microsoft.azure.storage.AuthenticationScheme; import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RequestOptions; @@ -42,7 +41,6 @@ * @param * The type of the expected result */ -@SuppressWarnings("deprecation") public abstract class StorageRequest { /** * Holds a reference to a realized exception which occurred during execution. @@ -309,23 +307,12 @@ protected final StorageException materializeException(final OperationContext opC public static final void signBlobQueueAndFileRequest(HttpURLConnection request, ServiceClient client, long contentLength, OperationContext context) throws InvalidKeyException, StorageException { - if (client.getAuthenticationScheme() == AuthenticationScheme.SHAREDKEYFULL) { - StorageCredentialsHelper.signBlobAndQueueRequest(client.getCredentials(), request, contentLength, context); - } - else { - StorageCredentialsHelper.signBlobAndQueueRequestLite(client.getCredentials(), request, contentLength, - context); - } + StorageCredentialsHelper.signBlobQueueAndFileRequest(client.getCredentials(), request, contentLength, context); } public static final void signTableRequest(HttpURLConnection request, ServiceClient client, long contentLength, OperationContext context) throws InvalidKeyException, StorageException { - if (client.getAuthenticationScheme() == AuthenticationScheme.SHAREDKEYFULL) { - StorageCredentialsHelper.signTableRequest(client.getCredentials(), request, contentLength, context); - } - else { - StorageCredentialsHelper.signTableRequestLite(client.getCredentials(), request, contentLength, context); - } + StorageCredentialsHelper.signTableRequest(client.getCredentials(), request, contentLength, context); } public void applyLocationModeToRequest() { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableFullCanonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableFullCanonicalizer.java index 1fc25ca2747b9..59898118db2ea 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableFullCanonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableFullCanonicalizer.java @@ -39,7 +39,8 @@ final class TableFullCanonicalizer extends Canonicalizer { * @throws StorageException */ @Override - protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) throws StorageException { + protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) + throws StorageException { if (contentLength < -1) { throw new InvalidParameterException(SR.INVALID_CONTENT_LENGTH); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableLiteCanonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableLiteCanonicalizer.java index 221523d3d8560..9c6deda1acb92 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableLiteCanonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/TableLiteCanonicalizer.java @@ -47,7 +47,8 @@ class TableLiteCanonicalizer extends Canonicalizer { * @throws StorageException */ @Override - protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) throws StorageException { + protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength) + throws StorageException { if (contentLength < -1) { throw new InvalidParameterException(SR.INVALID_CONTENT_LENGTH); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Utility.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Utility.java index c1abeda3944f6..5d8a28f27eb29 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Utility.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Utility.java @@ -239,7 +239,7 @@ public static boolean areCredentialsEqual(final StorageCredentials thisCred, fin return true; } - if (thatCred == null || thisCred.getClass() != thatCred.getClass()) { + if (thisCred == null || thatCred == null || thisCred.getClass() != thatCred.getClass()) { return false; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index d81dfcfae6f00..bd3f8f2cc38bf 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -25,6 +25,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -34,14 +35,10 @@ import java.util.concurrent.TimeoutException; import com.microsoft.azure.storage.AccessCondition; +import com.microsoft.azure.storage.blob.BlobRequestOptions; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.DoesServiceRequest; -import com.microsoft.azure.storage.OperationContext; -import com.microsoft.azure.storage.StorageErrorCode; -import com.microsoft.azure.storage.StorageErrorCodeStrings; -import com.microsoft.azure.storage.StorageException; -import com.microsoft.azure.storage.StorageLocation; -import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.Base64; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.Logger; @@ -49,15 +46,26 @@ import com.microsoft.azure.storage.core.PathUtility; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.SharedAccessSignatureHelper; +import com.microsoft.azure.storage.core.StorageCredentialsHelper; import com.microsoft.azure.storage.core.StorageRequest; import com.microsoft.azure.storage.core.StreamMd5AndLength; +import com.microsoft.azure.storage.core.UriQueryBuilder; import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.DoesServiceRequest; +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.azure.storage.StorageErrorCode; +import com.microsoft.azure.storage.StorageErrorCodeStrings; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageLocation; +import com.microsoft.azure.storage.StorageUri; /** * Represents a Microsoft Azure File. */ public class CloudFile implements ListFileItem { - /** * Holds the number of bytes to buffer when writing to a {@link FileOutputStream}. */ @@ -91,18 +99,76 @@ public class CloudFile implements ListFileItem { /** * Holds the metadata for the file. */ - private HashMap metadata; + private HashMap metadata = new HashMap(); /** * Holds the properties of the file. */ - private FileProperties properties; + private FileProperties properties = new FileProperties(); /** * Stores the absolute URI to the file. */ private StorageUri storageUri; + /** + * Creates an instance of the CloudFile class using the specified absolute URI. + * + * @param fileAbsoluteUri + * A java.net.URI object that represents the absolute URI to the file. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFile(final URI fileAbsoluteUri) throws StorageException { + this(new StorageUri(fileAbsoluteUri)); + } + + /** + * Creates an instance of the CloudFile class using the specified absolute StorageUri. + * + * @param fileAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the file. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFile(final StorageUri fileAbsoluteUri) throws StorageException { + this(fileAbsoluteUri, (StorageCredentials)null); + } + + /** + * Creates an instance of the CloudFile class using the specified absolute URI + * and credentials. + * + * @param fileAbsoluteUri + * A java.net.URI object that represents the absolute URI to the file. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(fileAbsoluteUri), credentials); + } + + /** + * Creates an instance of the CloudFile class using the specified absolute StorageUri + * and credentials. + * + * @param fileAbsoluteUri + * A {@link StorageUri} object that represents the absolute URI to the file. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFile(final StorageUri fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { + this.parseQueryAndVerify(fileAbsoluteUri, credentials); + } + /** * Creates an instance of the CloudFile class using the specified absolute URI and storage service * client. @@ -110,12 +176,14 @@ public class CloudFile implements ListFileItem { * @param fileAbsoluteUri * A java.net.URI object that represents the absolute URI to the file. * @param client - * A {@link CloudFileClient} object that specifies the endpoint for the File service. + * A {@link CloudFileClient} object that specifies the endpoint for the file service. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFile#CloudFile(URI, StorageCredentials)} */ + @Deprecated public CloudFile(final URI fileAbsoluteUri, final CloudFileClient client) throws StorageException, URISyntaxException { this(new StorageUri(fileAbsoluteUri), client); @@ -127,26 +195,22 @@ public CloudFile(final URI fileAbsoluteUri, final CloudFileClient client) throws * @param fileAbsoluteUri * A {@link StorageUri} object that represents the absolute URI to the file. * @param client - * A {@link CloudFileClient} object that specifies the endpoint for the File service. + * A {@link CloudFileClient} object that specifies the endpoint for the file service. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFile#CloudFile(StorageUri, StorageCredentials)} */ + @Deprecated public CloudFile(final StorageUri fileAbsoluteUri, final CloudFileClient client) throws StorageException, URISyntaxException { - Utility.assertNotNull("fileAbsoluteUri", fileAbsoluteUri); - - this.metadata = new HashMap(); - this.properties = new FileProperties(); - this.fileServiceClient = client; - this.storageUri = fileAbsoluteUri; - - this.parseURIQueryStringAndVerify( - this.storageUri, - client, - client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) : client - .isUsePathStyleUris()); + this.parseQueryAndVerify(fileAbsoluteUri, client == null ? null : client.getCredentials()); + + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.fileServiceClient = client; + } } /** @@ -156,14 +220,16 @@ public CloudFile(final StorageUri fileAbsoluteUri, final CloudFileClient client) * @param fileAbsoluteUri * A java.net.URI object that represents the absolute URI to the file. * @param client - * A {@link CloudFileClient} object that specifies the endpoint for the File service. + * A {@link CloudFileClient} object that specifies the endpoint for the file service. * @param share * A {@link CloudFileShare} object that represents the share to use for the file. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFile#CloudFile(URI, StorageCredentials)} */ + @Deprecated public CloudFile(final URI fileAbsoluteUri, final CloudFileClient client, final CloudFileShare share) throws StorageException, URISyntaxException { this(new StorageUri(fileAbsoluteUri), client, share); @@ -176,14 +242,16 @@ public CloudFile(final URI fileAbsoluteUri, final CloudFileClient client, final * @param fileAbsoluteUri * A {@link StorageUri} object that represents the absolute URI to the file. * @param client - * A {@link CloudFileClient} object that specifies the endpoint for the File service. + * A {@link CloudFileClient} object that specifies the endpoint for the file service. * @param share * A {@link CloudFileShare} object that represents the share to use for the file. * * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFile#CloudFile(StorageUri, StorageCredentials)} */ + @Deprecated public CloudFile(final StorageUri fileAbsoluteUri, final CloudFileClient client, final CloudFileShare share) throws StorageException, URISyntaxException { this(fileAbsoluteUri, client); @@ -214,6 +282,300 @@ public CloudFile(final CloudFile otherFile) { this.setStreamWriteSizeInBytes(otherFile.getStreamWriteSizeInBytes()); } + /** + * Aborts an ongoing Azure File copy operation. + * + * @param copyId + * A String object that identifies the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void abortCopy(final String copyId) throws StorageException { + this.abortCopy(copyId, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Aborts an ongoing Azure File copy operation. + * + * @param copyId + * A String object that identifies the copy operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the Azure File. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void abortCopy(final String copyId, final AccessCondition accessCondition, FileRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + this.abortCopyImpl(copyId, accessCondition, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest abortCopyImpl(final String copyId, + final AccessCondition accessCondition, final FileRequestOptions options) { + Utility.assertNotNull("copyId", copyId); + + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) + throws Exception { + return FileRequest.abortCopy(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition, copyId); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context); + } + + @Override + public Void preProcessResponse(CloudFile parentObject, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + return null; + } + }; + + return putRequest; + } + + /** + * Requests the service to start copying a blob's contents, properties, and metadata to a new file. + * + * @param sourceBlob + * A CloudBlob object that represents the source blob to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudBlob sourceBlob) throws StorageException, URISyntaxException { + return this.startCopy(sourceBlob, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a file's contents, properties, and metadata to a new file, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param sourceBlob + * A CloudBlob object that represents the source blob to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination file. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceBlob", sourceBlob); + return this.startCopy( + sourceBlob.getQualifiedUri(), sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Requests the service to start copying an Azure File's contents, properties, and metadata to a new Azure File. + * + * @param sourceFile + * A CloudFile object that represents the source Azure File to copy. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + */ + @DoesServiceRequest + public final String startCopy(final CloudFile sourceFile) throws StorageException, URISyntaxException { + return this.startCopy(sourceFile, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying an Azure File's contents, properties, and metadata to a new Azure File, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param sourceFile + * A CloudFile object that represents the source file to copy. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudFile sourceFile, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + Utility.assertNotNull("sourceFile", sourceFile); + return this.startCopy(sourceFile.getTransformedAddress(opContext).getPrimaryUri(), + sourceAccessCondition, destinationAccessCondition, options, opContext); + + } + + /** + * Requests the service to start copying a URI's contents, properties, and metadata to a new Azure File. + * + * @param source + * The source's java.net.URI. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String startCopy(final URI source) throws StorageException { + return this.startCopy(source, null /* sourceAccessCondition */, + null /* destinationAccessCondition */, null /* options */, null /* opContext */); + } + + /** + * Requests the service to start copying a URI's contents, properties, and metadata to a new Azure File, + * using the specified access conditions, lease ID, request options, and operation context. + * + * @param source + * The source's java.net.URI. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final String startCopy(final URI source, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + this.startCopyImpl(source, sourceAccessCondition, destinationAccessCondition, options), + options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest startCopyImpl(final URI source, + final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, + final FileRequestOptions options) { + + if (sourceAccessCondition != null && !Utility.isNullOrEmpty(sourceAccessCondition.getLeaseID())) { + throw new IllegalArgumentException(SR.LEASE_CONDITION_ON_SOURCE); + } + + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) + throws Exception { + return FileRequest.copyFrom(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, sourceAccessCondition, destinationAccessCondition, source.toASCIIString()); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudFile file, OperationContext context) { + FileRequest.addMetadata(connection, file.metadata, context); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context); + } + + @Override + public String preProcessResponse(CloudFile file, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + file.properties.setCopyState(FileResponse.getCopyState(this.getConnection())); + + return file.properties.getCopyState().getCopyId(); + } + }; + + return putRequest; + } + /** * Clears a range from a file. *

    @@ -264,7 +626,7 @@ public void clearRange(final long offset, final long length, final AccessConditi opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); @@ -273,7 +635,7 @@ public void clearRange(final long offset, final long length, final AccessConditi } /** - * Creates a file. + * Creates a file. If the file already exists, this will replace it. * * @param size * A long which represents the size, in bytes, of the file. @@ -287,7 +649,8 @@ public void create(final long size) throws StorageException { } /** - * Creates a file using the specified access condition, request options and operation context. + * Creates a file using the specified access condition, request options and operation context. If the file already + * exists, this will replace it. * * @param size * A long which represents the size, in bytes, of the file. @@ -313,7 +676,7 @@ public void create(final long size, final AccessCondition accessCondition, FileR opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.createImpl(size, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -321,8 +684,8 @@ public void create(final long size, final AccessCondition accessCondition, FileR private StorageRequest createImpl(final long size, final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) @@ -339,7 +702,7 @@ public void setHeaders(HttpURLConnection connection, CloudFile file, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -395,7 +758,7 @@ public final void delete(final AccessCondition accessCondition, FileRequestOptio } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.deleteImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -437,7 +800,7 @@ public final boolean deleteIfExists() throws StorageException { @DoesServiceRequest public final boolean deleteIfExists(final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true, accessCondition, options, opContext); if (exists) { @@ -463,8 +826,8 @@ public final boolean deleteIfExists(final AccessCondition accessCondition, FileR private StorageRequest deleteImpl(final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest deleteRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest deleteRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) @@ -476,7 +839,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -533,7 +896,7 @@ public final void download(final OutputStream outStream, final AccessCondition a } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadToStreamImpl( null /* fileOffset */, null /* length */, outStream, accessCondition, options, opContext), options @@ -593,7 +956,7 @@ public final void downloadRange(final long offset, final Long length, final Outp } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadToStreamImpl(offset, length, outStream, accessCondition, options, opContext), @@ -633,7 +996,7 @@ protected final int downloadRangeInternal(final long fileOffset, final Long leng opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadToByteArrayImpl(fileOffset, length, buffer, bufferOffset, accessCondition, options, opContext), options.getRetryPolicyFactory(), @@ -649,15 +1012,15 @@ protected final int downloadRangeInternal(final long fileOffset, final Long leng * A Long which represents the number of bytes to read or null. * @param buffer * A byte array which represents the buffer to which the file bytes are downloaded. - * @param bufferOffet + * @param bufferOffset * An int which represents the byte offset to use as the starting point for the target. * * @throws StorageException */ @DoesServiceRequest public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer, - final int bufferOffet) throws StorageException { - return this.downloadRangeToByteArray(offset, length, buffer, bufferOffet, null /* accessCondition */, + final int bufferOffset) throws StorageException { + return this.downloadRangeToByteArray(offset, length, buffer, bufferOffset, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -714,16 +1077,16 @@ public final int downloadRangeToByteArray(final long offset, final Long length, * * @param buffer * A byte array which represents the buffer to which the file bytes are downloaded. - * @param bufferOffet + * @param bufferOffset * An int which represents the byte offset to use as the starting point for the target. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public final int downloadToByteArray(final byte[] buffer, final int bufferOffet) throws StorageException { + public final int downloadToByteArray(final byte[] buffer, final int bufferOffset) throws StorageException { return this - .downloadToByteArray(buffer, bufferOffet, null /* accessCondition */, null /* options */, null /* opContext */); + .downloadToByteArray(buffer, bufferOffset, null /* accessCondition */, null /* options */, null /* opContext */); } /** @@ -732,7 +1095,7 @@ public final int downloadToByteArray(final byte[] buffer, final int bufferOffet) * * @param buffer * A byte array which represents the buffer to which the file bytes are downloaded. - * @param bufferOffet + * @param bufferOffset * A long which represents the byte offset to use as the starting point for the target. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. @@ -767,7 +1130,7 @@ public final int downloadToByteArray(final byte[] buffer, final int bufferOffset } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadToByteArrayImpl(null, null, buffer, bufferOffset, accessCondition, options, opContext), @@ -919,8 +1282,8 @@ public ArrayList downloadFileRanges() throws StorageException { * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * - * @return An ArrayList object which represents the set of file ranges and their starting and ending - * byte offsets. + * @return An ArrayList object which represents the set of file ranges and their starting + * and ending byte offsets. * * @throws StorageException * If a storage service error occurred. @@ -932,7 +1295,7 @@ public ArrayList downloadFileRanges(final AccessCondition accessCondi opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadFileRangesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -940,8 +1303,8 @@ public ArrayList downloadFileRanges(final AccessCondition accessCondi private StorageRequest> downloadFileRangesImpl( final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest> getRequest = new StorageRequest>( - options, this.getStorageUri()) { + final StorageRequest> getRequest = + new StorageRequest>(options, this.getStorageUri()) { @Override public void setRequestLocationMode() { @@ -958,7 +1321,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1002,7 +1365,8 @@ public void setRequestLocationMode() { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { - // The first time this is called, we have to set the length and file offset. On retries, these will already have values and need not be called. + // The first time this is called, we have to set the length and offset. + // On retries, these will already have values and need not be called. if (this.getOffset() == null) { this.setOffset(fileOffset); } @@ -1022,7 +1386,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1149,7 +1513,8 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { - // The first time this is called, we have to set the length and file offset. On retries, these will already have values and need not be called. + // The first time this is called, we have to set the length and offset. + // On retries, these will already have values and need not be called. if (this.getOffset() == null) { this.setOffset(fileOffset); } @@ -1170,7 +1535,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1238,7 +1603,7 @@ public void recoveryAction(OperationContext context) throws IOException { private Integer preProcessDownloadResponse(final StorageRequest request, final FileRequestOptions options, final CloudFileClient client, final CloudFile file, - final OperationContext context, final boolean isRangeGet) throws StorageException { + final OperationContext context, final boolean isRangeGet) throws Exception { if (request.getResult().getStatusCode() != HttpURLConnection.HTTP_PARTIAL && request.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { request.setNonExceptionedRetryableFailure(true); @@ -1281,19 +1646,19 @@ private Integer preProcessDownloadResponse(final StorageRequest - * This method populates the file's system properties and user-defined metadata. Before reading or modifying a - * file's properties or metadata, call this method or its overload to retrieve the latest values for the file's - * properties and metadata from the Microsoft Azure storage service. + * This method populates the file's system properties and user-defined metadata. Before reading or modifying + * a file's properties or metadata, call this method or its overload to retrieve the latest values for the + * file's properties and metadata from the Microsoft Azure storage service. * * @throws StorageException * If a storage service error occurred. @@ -1306,9 +1671,9 @@ public final void downloadAttributes() throws StorageException { /** * Populates a file's properties and metadata using the specified request options and operation context. *

    - * This method populates the file's system properties and user-defined metadata. Before reading or modifying a - * file's properties or metadata, call this method or its overload to retrieve the latest values for the file's - * properties and metadata from the Microsoft Azure storage service. + * This method populates the file's system properties and user-defined metadata. Before reading or modifying + * a file's properties or metadata, call this method or its overload to retrieve the latest values for + * the file's properties and metadata from the Microsoft Azure storage service. * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. @@ -1331,7 +1696,7 @@ public final void downloadAttributes(final AccessCondition accessCondition, File opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1339,8 +1704,8 @@ public final void downloadAttributes(final AccessCondition accessCondition, File private StorageRequest downloadAttributesImpl( final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest getRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public void setRequestLocationMode() { @@ -1351,14 +1716,14 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { return FileRequest.getFileProperties( - file.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, - accessCondition); + file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition); } @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1428,7 +1793,7 @@ private final boolean exists(final boolean primaryOnly, final AccessCondition ac } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -1436,8 +1801,8 @@ private final boolean exists(final boolean primaryOnly, final AccessCondition ac private StorageRequest existsImpl(final boolean primaryOnly, final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest getRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public void setRequestLocationMode() { this.setRequestLocationMode(primaryOnly ? RequestLocationMode.PRIMARY_ONLY @@ -1448,14 +1813,14 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { return FileRequest.getFileProperties( - file.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, - accessCondition); + file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition); } @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1483,10 +1848,104 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { return getRequest; } + /** + * Returns a shared access signature for the file using the specified group policy identifier and + * shared access file headers. Note this does not contain the leading "?". + * + * @param policy + * A {@link SharedAccessFilePolicy} object that represents the access policy for the shared + * access signature. + * @param groupPolicyIdentifier + * A String that represents the share-level access policy. + * + * @return A String that represents the shared access signature. + * + * @throws InvalidKeyException + * If the credentials are invalid. + * @throws StorageException + * If a storage service error occurred. + */ + public String generateSharedAccessSignature(final SharedAccessFilePolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { + return generateSharedAccessSignature(policy, null /* headers */, groupPolicyIdentifier); + } + + /** + * Returns a shared access signature for the file using the specified group policy identifier and + * shared access file headers. Note this does not contain the leading "?". + * + * @param policy + * A {@link SharedAccessFilePolicy} object that represents the access policy for the shared + * access signature. + * @param headers + * A {@link SharedAccessFileHeaders} object that represents the optional header values to + * set for a file accessed with this shared access signature. + * @param groupPolicyIdentifier + * A String that represents the share-level access policy. + * + * @return A String that represents the shared access signature. + * + * @throws IllegalArgumentException + * If the credentials are invalid. + * @throws InvalidKeyException + * If the credentials are invalid. + * @throws StorageException + * If a storage service error occurred. + */ + public String generateSharedAccessSignature(final SharedAccessFilePolicy policy, + final SharedAccessFileHeaders headers, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { + if (!StorageCredentialsHelper.canCredentialsSignRequest(this.fileServiceClient.getCredentials())) { + throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY); + } + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile( + policy, headers, groupPolicyIdentifier, this.getCanonicalName(), this.fileServiceClient); + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile( + policy, headers, groupPolicyIdentifier, "f", signature); + + return builder.toString(); + } + + /** + * Returns the canonical name of the file in the format of + * /<service-name>/<account-name>/<share-name>/<file-name>. + *

    + * This format is used for Shared Access operations. + * + * @return The canonical name in the format of /<service-name>/<account-name> + * /<share-name>/<file-name>. + */ + String getCanonicalName() { + StringBuilder canonicalName = new StringBuilder("/"); + canonicalName.append(SR.FILE); + + String rawPath = this.getUri().getRawPath(); + if (this.fileServiceClient.isUsePathStyleUris()) { + canonicalName.append(rawPath); + } + else { + canonicalName.append(PathUtility.getCanonicalPathFromCredentials( + this.getServiceClient().getCredentials(), rawPath)); + } + + return canonicalName.toString(); + } + + /** + * Returns the Azure File's copy state. + * + * @return A {@link CopyState} object that represents the copy state of the file. + */ + public CopyState getCopyState() { + return this.properties.getCopyState(); + } + /** * Opens a file input stream to download the file. *

    - * Use {@link CloudFileClient#setStreamMinimumReadSizeInBytes} to configure the read size. + * Use {@link CloudFile#setStreamMinimumReadSizeInBytes(int)} to configure the read size. * * @return An InputStream object that represents the stream to use for reading from the file. * @@ -1499,9 +1958,10 @@ public final FileInputStream openRead() throws StorageException { } /** - * Opens a file input stream to download the file using the specified request options and operation context. + * Opens a file input stream to download the file using the specified request options and + * operation context. *

    - * Use {@link CloudFileClient#setStreamMinimumReadSizeInBytes} to configure the read size. + * Use {@link CloudFile#setStreamMinimumReadSizeInBytes(int)} to configure the read size. * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. @@ -1514,7 +1974,7 @@ public final FileInputStream openRead() throws StorageException { * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * - * @return An InputStream object that represents the stream to use for reading from the File. + * @return An InputStream object that represents the stream to use for reading from the file. * * @throws StorageException * If a storage service error occurred. @@ -1526,13 +1986,14 @@ public final FileInputStream openRead(final AccessCondition accessCondition, Fil opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient, false /* setStartTime */); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient, false /* setStartTime */); return new FileInputStream(this, accessCondition, options, opContext); } /** - * Opens an output stream object to write data to the file. The file must already exist. + * Opens an output stream object to write data to the file. The file must already exist and any existing data may + * be overwritten. * * @return A {@link FileOutputStream} object used to write data to the file. * @@ -1547,7 +2008,7 @@ public FileOutputStream openWriteExisting() throws StorageException { /** * Opens an output stream object to write data to the file, using specified request options and - * operation context. The file must already exist. + * operation context. The file must already exist and any existing data may be overwritten. * * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the file. @@ -1574,7 +2035,11 @@ public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileR /** * Opens an output stream object to write data to the file. The file does not yet exist and will - * be created with the length specified. + * be created with the length specified. If the file already exists on the service, it will be overwritten. + *

    + * To avoid overwriting and instead throw an error, please use the + * {@link #openWriteNew(long, AccessCondition, FileRequestOptions, OperationContext)} overload with the appropriate + * {@link AccessCondition}. * * @param length * A long which represents the length, in bytes, of the stream to create. @@ -1592,7 +2057,11 @@ public FileOutputStream openWriteNew(final long length) throws StorageException /** * Opens an output stream object to write data to the file, using the specified lease ID, request options and - * operation context. The file does not need to yet exist and will be created with the length specified. + * operation context. The file does not need to yet exist and will be created with the length specified. If the file + * already exists on the service, it will be overwritten. + *

    + * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. * * @param length * A long which represents the length, in bytes, of the stream to create. @@ -1647,7 +2116,7 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a if (opContext == null) { opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient, false /* setStartTime */); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient, false /* setStartTime */); if (length != null) { if (options.getStoreFileContentMD5()) { @@ -1669,7 +2138,7 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a } /** - * Uploads a file from data in a byte array. + * Uploads a file from data in a byte array. If the file already exists on the service, it will be overwritten. * * @param buffer * A byte array which represents the data to write to the file. @@ -1688,7 +2157,7 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int } /** - * Uploads a file from data in a byte array. + * Uploads a file from data in a byte array. If the file already exists on the service, it will be overwritten. * * @param buffer * A byte array which represents the data to write to the file. @@ -1720,7 +2189,7 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int } /** - * Uploads a file. + * Uploads a local file. If the file already exists on the service, it will be overwritten. * * @param path * A String which represents the path to the file to be uploaded. @@ -1734,7 +2203,7 @@ public void uploadFromFile(final String path) throws StorageException, IOExcepti } /** - * Uploads a file from a file. + * Uploads a file from a local file. If the file already exists on the service, it will be overwritten. * * @param path * A String which represents the path to the file to be uploaded. @@ -1763,7 +2232,8 @@ public void uploadFromFile(final String path, final AccessCondition accessCondit } /** - * Uploads a file from a string using the platform's default encoding. + * Uploads a file from a string using the platform's default encoding. If the file already exists on the service, it + * will be overwritten. * * @param content * A String which represents the content that will be uploaded to the file. @@ -1777,7 +2247,8 @@ public void uploadText(final String content) throws StorageException, IOExceptio } /** - * Uploads a file from a string using the specified encoding. + * Uploads a file from a string using the specified encoding. If the file already exists on the service, it will be + * overwritten. * * @param content * A String which represents the content that will be uploaded to the file. @@ -1806,10 +2277,10 @@ public void uploadText(final String content, final String charsetName, final Acc } /** - * Uploads a range to a file. + * Uploads a range to a file. * * @param sourceStream - * An {@link IntputStream} object which represents the input stream to write to the file. + * An {@link InputStream} object which represents the input stream to write to the file. * @param offset * A long which represents the offset, in number of bytes, at which to begin writing the * data. @@ -1831,7 +2302,7 @@ public void uploadRange(final InputStream sourceStream, final long offset, final * Uploads a range to a file using the specified lease ID, request options, and operation context. * * @param sourceStream - * An {@link IntputStream} object which represents the input stream to write to the file. + * An {@link InputStream} object which represents the input stream to write to the file. * @param offset * A long which represents the offset, in number of bytes, at which to begin writing the * data. @@ -1861,7 +2332,7 @@ public void uploadRange(final InputStream sourceStream, final long offset, final opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); final byte[] data = new byte[(int) length]; @@ -1929,8 +2400,8 @@ private void putRangeInternal(final FileRange range, final FileRangeOperationTyp private StorageRequest putRangeImpl(final FileRange range, final FileRangeOperationType operationType, final byte[] data, final long length, final String md5, final AccessCondition accessCondition, final FileRequestOptions options, final OperationContext opContext) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) @@ -1957,10 +2428,10 @@ public void setHeaders(HttpURLConnection connection, CloudFile file, OperationCo public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { if (operationType == FileRangeOperationType.UPDATE) { - StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context); } else { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } } @@ -2024,7 +2495,7 @@ public final void uploadMetadata(final AccessCondition accessCondition, FileRequ } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2038,8 +2509,9 @@ private StorageRequest uploadMetadataImpl(fina @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { - return FileRequest.setFileMetadata(file.getTransformedAddress(context) - .getUri(this.getCurrentLocation()), options, context, accessCondition); + return FileRequest.setFileMetadata( + file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition); } @Override @@ -2050,7 +2522,7 @@ public void setHeaders(HttpURLConnection connection, CloudFile file, OperationCo @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2111,7 +2583,7 @@ public final void uploadProperties(final AccessCondition accessCondition, FileRe } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.uploadPropertiesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2119,21 +2591,21 @@ public final void uploadProperties(final AccessCondition accessCondition, FileRe private StorageRequest uploadPropertiesImpl( final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { return FileRequest.setFileProperties( - file.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, - accessCondition, file.properties); + file.getTransformedAddress(context).getUri(this.getCurrentLocation()), + options, context, accessCondition, file.properties); } @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @@ -2192,7 +2664,7 @@ public void resize(long size, AccessCondition accessCondition, FileRequestOption } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.resizeImpl(size, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -2200,8 +2672,8 @@ public void resize(long size, AccessCondition accessCondition, FileRequestOption private StorageRequest resizeImpl(final long size, final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) @@ -2213,7 +2685,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -2234,10 +2706,10 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation } /** - * Uploads the source stream data to the file. + * Uploads the source stream data to the file. If the file already exists on the service, it will be overwritten. * * @param sourceStream - * An {@link IntputStream} object to read from. + * An {@link InputStream} object to read from. * @param length * A long which represents the length, in bytes, of the stream data. Must be non zero. * @@ -2253,10 +2725,10 @@ public void upload(final InputStream sourceStream, final long length) throws Sto /** * Uploads the source stream data to the file using the specified access condition, request options, and operation - * context. + * context. If the file already exists on the service, it will be overwritten. * * @param sourceStream - * An {@link IntputStream} object to read from. + * An {@link InputStream} object to read from. * @param length * A long which represents the length, in bytes, of the stream data. This must be great than * zero. @@ -2283,7 +2755,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); if (length <= 0) { throw new IllegalArgumentException(SR.INVALID_FILE_LENGTH); @@ -2351,7 +2823,7 @@ protected static String getParentNameFromURI(final StorageUri resourceAddress, f } else { // Case 3 ///[/]* - // Parent of File is Directory + // Parent of CloudFile is CloudFileDirectory parentName = relativeURIString.substring(0, lastDelimiterDex); if (parentName != null && parentName.equals(shareName)) { parentName = ""; @@ -2363,31 +2835,41 @@ protected static String getParentNameFromURI(final StorageUri resourceAddress, f } /** - * Strips the query and verifies the URI is absolute. + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. * * @param completeUri * A {@link StorageUri} object which represents the complete URI. - * @param existingClient - * A {@link CloudFileClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise, false. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - private void parseURIQueryStringAndVerify(final StorageUri completeUri, final CloudFileClient existingClient, - final boolean usePathStyleUri) throws StorageException, URISyntaxException { - Utility.assertNotNull("completeUri", completeUri); + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { + Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - this.fileServiceClient = (existingClient == null) ? new CloudFileClient( - PathUtility.getServiceClientBaseAddress(this.storageUri, usePathStyleUri), null) : existingClient; - this.name = PathUtility.getFileNameFromURI(completeUri.getPrimaryUri(), usePathStyleUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); + + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); + } + + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.fileServiceClient = new CloudFileClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getFileNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris); + } + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); + } } protected void updateEtagAndLastModifiedFromResponse(HttpURLConnection request) { @@ -2419,6 +2901,7 @@ protected void updateLengthFromResponse(HttpURLConnection request) { * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") @Override public final CloudFileShare getShare() throws StorageException, URISyntaxException { if (this.share == null) { @@ -2445,9 +2928,6 @@ public final HashMap getMetadata() { * @return A String that represents the name of the file. */ public final String getName() { - if (Utility.isNullOrEmpty(this.name)) { - this.name = PathUtility.getFileNameFromURI(this.getUri(), this.fileServiceClient.isUsePathStyleUris()); - } return this.name; } @@ -2461,6 +2941,7 @@ public final String getName() { * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") @Override public final CloudFileDirectory getParent() throws URISyntaxException, StorageException { if (this.parent == null) { @@ -2484,7 +2965,7 @@ public final FileProperties getProperties() { } /** - * Returns the File service client associated with the file. + * Returns the file service client associated with the file. * * @return A {@link CloudFileClient} object that represents the client. */ @@ -2576,8 +3057,8 @@ protected void setStorageUri(final StorageUri storageUri) { * Sets the minimum read size when using a {@link FileInputStream}. * * @param minimumReadSize - * An int that represents the minimum number of bytes to buffer when reading from a file - * while using a {@link FileInputStream} object. Must be greater than or equal to 16 KB. + * An int that represents the minimum number of bytes to buffer when reading from + * a file while using a {@link FileInputStream} object. Must be greater than or equal to 16 KB. * @throws IllegalArgumentException * If minimumReadSize is less than 16 KB. */ @@ -2590,7 +3071,7 @@ public void setStreamMinimumReadSizeInBytes(final int minimumReadSize) { } /** - * Sets the number of bytes to buffer when writing to a {@link FileOutoutStream}. + * Sets the number of bytes to buffer when writing to a {@link FileOutputStream}. * * @param streamWriteSizeInBytes * An int which represents the number of bytes to buffer while using a @@ -2622,8 +3103,8 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) { * @throws URISyntaxException * If the resource URI is invalid. */ - protected final StorageUri getTransformedAddress(final OperationContext opContext) throws URISyntaxException, - StorageException { + protected final StorageUri getTransformedAddress(final OperationContext opContext) + throws URISyntaxException, StorageException { return this.fileServiceClient.getCredentials().transformUri(this.getStorageUri(), opContext); } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileClient.java index 9d0b33fd58e4c..60ebcc78cba55 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileClient.java @@ -19,20 +19,20 @@ import java.net.URISyntaxException; import com.microsoft.azure.storage.DoesServiceRequest; -import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultContinuationType; import com.microsoft.azure.storage.ResultSegment; -import com.microsoft.azure.storage.RetryExponentialRetry; import com.microsoft.azure.storage.ServiceClient; import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsAnonymous; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.LazySegmentedIterable; import com.microsoft.azure.storage.core.ListResponse; import com.microsoft.azure.storage.core.ListingContext; +import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.SegmentedStorageRequest; import com.microsoft.azure.storage.core.StorageRequest; import com.microsoft.azure.storage.core.Utility; @@ -48,7 +48,7 @@ public final class CloudFileClient extends ServiceClient { /** * Holds the default request option values associated with this Service Client. */ - private FileRequestOptions defaultRequestOptions; + private FileRequestOptions defaultRequestOptions = new FileRequestOptions(); /** * Creates an instance of the CloudFileClient class using the specified File service endpoint and @@ -76,10 +76,10 @@ public CloudFileClient(final URI baseUri, StorageCredentials credentials) { */ public CloudFileClient(StorageUri storageUri, StorageCredentials credentials) { super(storageUri, credentials); - this.defaultRequestOptions = new FileRequestOptions(); - this.defaultRequestOptions.setLocationMode(LocationMode.PRIMARY_ONLY); - this.defaultRequestOptions.setRetryPolicyFactory(new RetryExponentialRetry()); - this.defaultRequestOptions.setConcurrentRequestCount(FileConstants.DEFAULT_CONCURRENT_REQUEST_COUNT); + if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) { + throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS); + } + FileRequestOptions.applyDefaults(this.defaultRequestOptions); } /** @@ -98,6 +98,7 @@ public CloudFileClient(StorageUri storageUri, StorageCredentials credentials) { * @see Naming and Referencing Shares, * Directories, Files, and Metadata */ + @SuppressWarnings("deprecation") public CloudFileShare getShareReference(final String shareName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("shareName", shareName); return new CloudFileShare(shareName, this); @@ -255,7 +256,7 @@ private Iterable listSharesWithPrefix(final String prefix, } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this); + options = FileRequestOptions.populateAndApplyDefaults(options, this); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); @@ -302,7 +303,7 @@ private ResultSegment listSharesWithPrefixSegmented(final String } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this); + options = FileRequestOptions.populateAndApplyDefaults(options, this); Utility.assertContinuationType(continuationToken, ResultContinuationType.SHARE); @@ -343,7 +344,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, Void parentObject, @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -382,6 +383,103 @@ public ResultSegment postProcessResponse(HttpURLConnection conne return getRequest; } + /** + * Retrieves the current {@link FileServiceProperties} for the given storage service. This encapsulates + * the CORS configurations. + * + * @return A {@link FileServiceProperties} object representing the current configuration of the service. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final FileServiceProperties downloadServiceProperties() throws StorageException { + return this.downloadServiceProperties(null /* options */, null /* opContext */); + } + + /** + * Retrieves the current {@link FileServiceProperties} for the given storage service. This encapsulates + * the CORS configurations. + * + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client + * ({@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link FileServiceProperties} object representing the current configuration of the service. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final FileServiceProperties downloadServiceProperties(FileRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this); + + return new FileServiceProperties(ExecutionEngine.executeWithRetry( + this, null, this.downloadServicePropertiesImpl(options, false), + options.getRetryPolicyFactory(), opContext)); + } + + /** + * Uploads a new {@link FileServiceProperties} configuration to the given storage service. This encapsulates + * the CORS configurations. + * + * @param properties + * A {@link FileServiceProperties} object which specifies the service properties to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadServiceProperties(final FileServiceProperties properties) throws StorageException { + this.uploadServiceProperties(properties, null /* options */, null /* opContext */); + } + + /** + * Uploads a new {@link FileServiceProperties} configuration to the given storage service. This encapsulates + * the CORS configurations. + * + * @param properties + * A {@link FileServiceProperties} object which specifies the service properties to upload. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client + * ({@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadServiceProperties(final FileServiceProperties properties, FileRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this); + + Utility.assertNotNull("properties", properties); + + ExecutionEngine.executeWithRetry(this, null, + this.uploadServicePropertiesImpl(properties.getServiceProperties(), options, opContext, false), + options.getRetryPolicyFactory(), opContext); + } + /** * Gets the {@link FileRequestOptions} that is used for requests associated with this CloudFileClient * @@ -403,7 +501,7 @@ public void setDefaultRequestOptions(FileRequestOptions defaultRequestOptions) { Utility.assertNotNull("defaultRequestOptions", defaultRequestOptions); this.defaultRequestOptions = defaultRequestOptions; } - + /** * Indicates whether path-style URIs are being used. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java index bd3c1b04e2081..16937dfdeb848 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java @@ -20,6 +20,7 @@ import java.net.URISyntaxException; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.Constants; @@ -28,6 +29,8 @@ import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultContinuationType; import com.microsoft.azure.storage.ResultSegment; +import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; @@ -39,17 +42,17 @@ import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.SegmentedStorageRequest; +import com.microsoft.azure.storage.core.SharedAccessSignatureHelper; import com.microsoft.azure.storage.core.StorageRequest; import com.microsoft.azure.storage.core.Utility; /** * Represents a virtual directory of files. *

    - * Directories, which are encapsulated as {@link CloudFileDirectories} objects, hold files and can also contain + * Directories, which are encapsulated as {@link CloudFileDirectory} objects, hold files and can also contain * sub-directories. */ public final class CloudFileDirectory implements ListFileItem { - /** * Holds the share reference. */ @@ -74,12 +77,69 @@ public final class CloudFileDirectory implements ListFileItem { * Holds the name of the directory. */ private String name; + + /** + * Represents the directory metadata. + */ + private HashMap metadata = new HashMap(); /** * Holds a FileDirectoryProperties object that holds the directory's system properties. */ - private FileDirectoryProperties properties; + private FileDirectoryProperties properties = new FileDirectoryProperties(); + + /** + * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory. + * + * @param directoryAbsoluteUri + * A {@link URI} that represents the file directory's address. + * @throws StorageException + */ + public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException { + this(new StorageUri(directoryAbsoluteUri)); + } + + /** + * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory. + * + * @param directoryAbsoluteUri + * A {@link StorageUri} that represents the file directory's address. + * @throws StorageException + */ + public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException { + this(directoryAbsoluteUri, (StorageCredentials) null); + } + + /** + * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory + * and credentials. + * + * @param directoryAbsoluteUri + * A {@link URI} that represents the file directory's address. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + */ + public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentials credentials) + throws StorageException { + this(new StorageUri(directoryAbsoluteUri), credentials); + } + /** + * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory + * and credentials. + * + * @param directoryAbsoluteUri + * A {@link StorageUri} that represents the file directory's address. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * @throws StorageException + */ + public CloudFileDirectory(final StorageUri directoryAbsoluteUri, final StorageCredentials credentials) + throws StorageException { + this.parseQueryAndVerify(directoryAbsoluteUri, credentials); + } + /** * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory. * @@ -89,7 +149,9 @@ public final class CloudFileDirectory implements ListFileItem { * A {@link CloudFileClient} object that represents the associated service client. * @throws StorageException * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFileDirectory#CloudFileDirectory(URI, StorageCredentials)} */ + @Deprecated public CloudFileDirectory(final URI directoryAbsoluteUri, final CloudFileClient client) throws StorageException, URISyntaxException { this(new StorageUri(directoryAbsoluteUri), client); @@ -104,19 +166,17 @@ public CloudFileDirectory(final URI directoryAbsoluteUri, final CloudFileClient * A {@link CloudFileClient} object that represents the associated service client. * @throws StorageException * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFileDirectory#CloudFileDirectory(StorageUri, StorageCredentials)} */ + @Deprecated public CloudFileDirectory(final StorageUri directoryAbsoluteUri, final CloudFileClient client) throws StorageException, URISyntaxException { - Utility.assertNotNull("directoryAbsoluteUri", directoryAbsoluteUri); + this.parseQueryAndVerify(directoryAbsoluteUri, client == null ? null : client.getCredentials()); - this.fileServiceClient = client; - this.storageUri = directoryAbsoluteUri; - this.properties = new FileDirectoryProperties(); - this.parseQueryAndVerify( - directoryAbsoluteUri, - client, - client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) : client - .isUsePathStyleUris()); + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.fileServiceClient = client; + } } /** @@ -139,7 +199,6 @@ protected CloudFileDirectory(final StorageUri uri, final String directoryName, f this.fileServiceClient = share.getServiceClient(); this.share = share; this.storageUri = uri; - this.properties = new FileDirectoryProperties(); } /** @@ -175,7 +234,7 @@ public void create(FileRequestOptions options, OperationContext opContext) throw } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, createDirectoryImpl(options), options.getRetryPolicyFactory(), opContext); @@ -183,8 +242,8 @@ public void create(FileRequestOptions options, OperationContext opContext) throw private StorageRequest createDirectoryImpl( final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory, OperationContext context) throws Exception { @@ -192,11 +251,16 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context); return request; } + + @Override + public void setHeaders(HttpURLConnection connection, CloudFileDirectory directory, OperationContext context) { + FileRequest.addMetadata(connection, directory.getMetadata(), context); + } @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -207,10 +271,10 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli return null; } - // Set properties - final FileDirectoryProperties properties = FileResponse - .getFileDirectoryProperties(this.getConnection()); - directory.properties = properties; + // Set attributes + final FileDirectoryAttributes attributes = FileResponse + .getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris()); + directory.setProperties(attributes.getProperties()); return null; } }; @@ -250,7 +314,7 @@ public boolean createIfNotExists() throws StorageException { */ @DoesServiceRequest public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException { - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext); if (exists) { @@ -309,7 +373,7 @@ public void delete(AccessCondition accessCondition, FileRequestOptions options, } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteDirectoryImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -317,8 +381,8 @@ public void delete(AccessCondition accessCondition, FileRequestOptions options, private StorageRequest deleteDirectoryImpl( final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory, OperationContext context) throws Exception { @@ -329,7 +393,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -381,7 +445,7 @@ public boolean deleteIfExists() throws StorageException { @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); if (exists) { @@ -451,7 +515,7 @@ private boolean exists(final boolean primaryOnly, final AccessCondition accessCo } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -459,8 +523,8 @@ private boolean exists(final boolean primaryOnly, final AccessCondition accessCo private StorageRequest existsImpl(final boolean primaryOnly, final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest getRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public void setRequestLocationMode() { @@ -479,7 +543,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -516,6 +580,87 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { } } + /** + * Uploads the directory's metadata. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadMetadata() throws StorageException { + this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Uploads the directory's metadata using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the directory. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest uploadMetadataImpl( + final AccessCondition accessCondition, final FileRequestOptions options) { + final StorageRequest putRequest = + new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest( + CloudFileClient client, CloudFileDirectory directory, OperationContext context) throws Exception { + return FileRequest.setDirectoryMetadata(directory.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, accessCondition); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudFileDirectory directory, OperationContext context) { + FileRequest.addMetadata(connection, directory.getMetadata(), context); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + directory.updatePropertiesFromResponse(this.getConnection()); + return null; + } + }; + + return putRequest; + } + /** * Downloads the directory's properties. * @@ -552,7 +697,7 @@ public void downloadAttributes(AccessCondition accessCondition, FileRequestOptio } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -560,8 +705,8 @@ public void downloadAttributes(AccessCondition accessCondition, FileRequestOptio private StorageRequest downloadAttributesImpl( final AccessCondition accessCondition, final FileRequestOptions options) { - final StorageRequest getRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public void setRequestLocationMode() { @@ -575,11 +720,11 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, accessCondition); } - + @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -591,9 +736,10 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli } // Set properties - final FileDirectoryProperties properties = FileResponse - .getFileDirectoryProperties(this.getConnection()); - directory.properties = properties; + final FileDirectoryAttributes attributes = + FileResponse.getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris()); + directory.setMetadata(attributes.getMetadata()); + directory.setProperties(attributes.getProperties()); return null; } }; @@ -639,7 +785,7 @@ public Iterable listFilesAndDirectories(FileRequestOptions options } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); @@ -695,7 +841,7 @@ public ResultSegment listFilesAndDirectoriesSegmented(final Intege } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); Utility.assertContinuationType(continuationToken, ResultContinuationType.FILE); @@ -714,7 +860,8 @@ private StorageRequest> getRequest = new StorageRequest>( + final StorageRequest> getRequest = + new StorageRequest>( options, this.getStorageUri()) { @Override @@ -727,13 +874,15 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory OperationContext context) throws Exception { listingContext.setMarker(segmentedRequest.getToken() != null ? segmentedRequest.getToken() .getNextMarker() : null); - return FileRequest.listFilesAndDirectories(directory.getUri(), options, context, listingContext); + return FileRequest.listFilesAndDirectories( + directory.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, listingContext); } @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -786,6 +935,7 @@ public ResultSegment postProcessResponse(HttpURLConnection connect * @throws URISyntaxException * If the resource URI is invalid. */ + @SuppressWarnings("deprecation") public CloudFile getFileReference(final String fileName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("fileName", fileName); @@ -814,26 +964,6 @@ public CloudFileDirectory getDirectoryReference(final String itemName) throws UR return new CloudFileDirectory(subdirectoryUri, itemName, this.getShare()); } - /** - * Returns a reference to a {@link CloudFileDirectory} object that represents a subdirectory in this directory. - * - * @param itemName - * A String that represents the name of the subdirectory. - * - * @return A {@link CloudFileDirectory} object that represents a reference to the specified directory. - * - * @throws URISyntaxException - * If the resource URI is invalid. - * @throws StorageException - * - * @deprecated as of 2.0.0. Use {@link #getDirectoryReference()} instead. - */ - @Deprecated - public CloudFileDirectory getSubDirectoryReference(final String itemName) throws URISyntaxException, - StorageException { - return this.getDirectoryReference(itemName); - } - /** * Returns the URI for this directory. * @@ -871,6 +1001,16 @@ public CloudFileClient getServiceClient() { public String getName() { return this.name; } + + /** + * Returns the metadata for the directory. This value is initialized with the metadata from the directory + * by a call to {@link #downloadAttributes}, and is set on the directory with a call to {@link #uploadMetadata}. + * + * @return A java.util.HashMap object that represents the metadata for the directory. + */ + public HashMap getMetadata() { + return this.metadata; + } /** * Returns the {@link FileDirectoryProperties} object that holds the directory's system properties. @@ -921,6 +1061,18 @@ public CloudFileShare getShare() throws StorageException, URISyntaxException { return this.share; } + /** + * Sets the metadata collection of name-value pairs to be set on the directory with an {@link #uploadMetadata} call. + * This collection will overwrite any existing directory metadata. If this is set to an empty collection, the + * directory metadata will be cleared on an {@link #uploadMetadata} call. + * + * @param metadata + * A java.util.HashMap object that represents the metadata being assigned to the directory. + */ + public void setMetadata(HashMap metadata) { + this.metadata = metadata; + } + /** * Sets the share for the directory. * @@ -953,31 +1105,41 @@ protected void setStorageUri(final StorageUri storageUri) { } /** - * Strips the query and verifies the URI is absolute. + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. * * @param completeUri * A {@link StorageUri} object which represents the complete URI. - * @param existingClient - * A {@link CloudFileClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise, false. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - private void parseQueryAndVerify(final StorageUri completeUri, final CloudFileClient existingClient, - final boolean usePathStyleUri) throws StorageException, URISyntaxException { - Utility.assertNotNull("completeUri", completeUri); + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { + Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - this.fileServiceClient = (existingClient == null) ? new CloudFileClient( - PathUtility.getServiceClientBaseAddress(this.storageUri, usePathStyleUri), null) : existingClient; - this.name = PathUtility.getFileNameFromURI(completeUri.getPrimaryUri(), usePathStyleUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); + + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); + } + + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.fileServiceClient = new CloudFileClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getDirectoryNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris); + } + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); + } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java index 017945cdf2059..18abe6badaa76 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java @@ -15,17 +15,27 @@ package com.microsoft.azure.storage.file; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import javax.xml.stream.XMLStreamException; + import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.SharedAccessPolicyHandler; +import com.microsoft.azure.storage.SharedAccessPolicySerializer; +import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; @@ -33,7 +43,10 @@ import com.microsoft.azure.storage.core.PathUtility; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.SharedAccessSignatureHelper; +import com.microsoft.azure.storage.core.StorageCredentialsHelper; import com.microsoft.azure.storage.core.StorageRequest; +import com.microsoft.azure.storage.core.UriQueryBuilder; import com.microsoft.azure.storage.core.Utility; /** @@ -46,17 +59,17 @@ public final class CloudFileShare { /** * Represents the share metadata. */ - protected HashMap metadata; + private HashMap metadata = new HashMap(); /** * Holds the share properties. */ - FileShareProperties properties; + FileShareProperties properties = new FileShareProperties(); /** * Holds the name of the share. */ - String name; + String name = null; /** * Holds the list of URIs for all locations. @@ -68,18 +81,6 @@ public final class CloudFileShare { */ private CloudFileClient fileServiceClient; - /** - * Initializes a new instance of the CloudFileShare class. - * - * @param client - * A {@link CloudFileClient} which represents a reference to the associated service client. - */ - private CloudFileShare(final CloudFileClient client) { - this.metadata = new HashMap(); - this.properties = new FileShareProperties(); - this.fileServiceClient = client; - } - /** * Creates an instance of the CloudFileShare class using the specified name and client. * @@ -100,17 +101,73 @@ private CloudFileShare(final CloudFileClient client) { * * @see Naming and Referencing Shares, * Directories, Files, and Metadata + * @deprecated as of 3.0.0. Please use {@link CloudFileClient#getShareReference(String)} */ + @Deprecated public CloudFileShare(final String shareName, final CloudFileClient client) throws URISyntaxException, StorageException { - this(client); Utility.assertNotNull("client", client); Utility.assertNotNull("shareName", shareName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), shareName); - this.name = shareName; - this.parseQueryAndVerify(this.storageUri, client, client.isUsePathStyleUris()); + this.fileServiceClient = client; + } + + /** + * Creates an instance of the CloudFileShare class using the specified URI. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the share. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFileShare(final URI uri) throws StorageException { + this(new StorageUri(uri)); + } + + /** + * Creates an instance of the CloudFileShare class using the specified URI. + * + * @param storageUri + * A {@link StorageUri} object which represents the absolute URI of the share. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFileShare(final StorageUri storageUri) throws StorageException { + this(storageUri, (StorageCredentials) null); + } + + /** + * Creates an instance of the CloudFileShare class using the specified URI and credentials. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the share. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFileShare(final URI uri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(uri), credentials); + } + + /** + * Creates an instance of the CloudFileShare class using the specified StorageUri and credentials. + * + * @param storageUri + * A {@link StorageUri} object which represents the absolute StorageUri of the share. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials) throws StorageException { + this.parseQueryAndVerify(storageUri, credentials); } /** @@ -125,7 +182,9 @@ public CloudFileShare(final String shareName, final CloudFileClient client) thro * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFileShare#CloudFileShare(URI, StorageCredentials)} */ + @Deprecated public CloudFileShare(final URI uri, final CloudFileClient client) throws StorageException, URISyntaxException { this(new StorageUri(uri), client); } @@ -142,20 +201,17 @@ public CloudFileShare(final URI uri, final CloudFileClient client) throws Storag * @throws StorageException * If a storage service error occurred. * @throws URISyntaxException + * @deprecated as of 3.0.0. Please use {@link CloudFileShare#CloudFileShare(StorageUri, StorageCredentials)} */ + @Deprecated public CloudFileShare(final StorageUri storageUri, final CloudFileClient client) throws StorageException, URISyntaxException { - this(client); - - Utility.assertNotNull("storageUri", storageUri); + this.parseQueryAndVerify(storageUri, client == null ? null : client.getCredentials()); - this.storageUri = storageUri; - - this.parseQueryAndVerify( - this.storageUri, - client, - client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) : client - .isUsePathStyleUris()); + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.fileServiceClient = client; + } } /** @@ -191,20 +247,21 @@ public void create(FileRequestOptions options, OperationContext opContext) throw } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, createImpl(options), options.getRetryPolicyFactory(), opContext); } private StorageRequest createImpl(final FileRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { final HttpURLConnection request = FileRequest.createShare( - share.getTransformedAddress().getUri(this.getCurrentLocation()), options, context); + share.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, share.getProperties()); return request; } @@ -216,7 +273,7 @@ public void setHeaders(HttpURLConnection connection, CloudFileShare share, Opera @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -271,7 +328,7 @@ public boolean createIfNotExists() throws StorageException { */ @DoesServiceRequest public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException { - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext); if (exists) { @@ -330,7 +387,7 @@ public void delete(AccessCondition accessCondition, FileRequestOptions options, } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -350,7 +407,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare sha @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -402,7 +459,7 @@ public boolean deleteIfExists() throws StorageException { @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); if (exists) { @@ -462,7 +519,7 @@ public void downloadAttributes(AccessCondition accessCondition, FileRequestOptio } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -488,7 +545,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare sha @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -512,6 +569,191 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope return getRequest; } + /** + * Downloads the permission settings for the share. + * + * @return A {@link FileSharePermissions} object that represents the share's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public FileSharePermissions downloadPermissions() throws StorageException { + return this.downloadPermissions(null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Downloads the permissions settings for the share using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link FileSharePermissions} object that represents the share's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public FileSharePermissions downloadPermissions(AccessCondition accessCondition, FileRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + downloadPermissionsImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest downloadPermissionsImpl( + final AccessCondition accessCondition, final FileRequestOptions options) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { + + @Override + public void setRequestLocationMode() { + this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY); + } + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, + OperationContext context) throws Exception { + return FileRequest.getAcl(share.getTransformedAddress().getUri(this.getCurrentLocation()), options, + accessCondition, context); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); + } + + @Override + public FileSharePermissions preProcessResponse(CloudFileShare share, CloudFileClient client, + OperationContext context) throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + share.updatePropertiesFromResponse(this.getConnection()); + return new FileSharePermissions(); + } + + @Override + public FileSharePermissions postProcessResponse(HttpURLConnection connection, + CloudFileShare share, CloudFileClient client, OperationContext context, + FileSharePermissions shareAcl) throws Exception { + HashMap accessIds = SharedAccessPolicyHandler.getAccessIdentifiers( + this.getConnection().getInputStream(), SharedAccessFilePolicy.class); + + for (final String key : accessIds.keySet()) { + shareAcl.getSharedAccessPolicies().put(key, accessIds.get(key)); + } + + return shareAcl; + } + }; + + return getRequest; + } + + /** + * Queries the service for this share's {@link ShareStats}. + * + * @return A {@link ShareStats} object for the given storage service. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public ShareStats getStats() throws StorageException { + return getStats(null /* options */, null /* context */); + } + + /** + * Queries the service for this share's {@link ShareStats}. + * + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client + * ({@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link ShareStats} object for the given storage service. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public ShareStats getStats(FileRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + getStatsImpl(options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest getStatsImpl(final FileRequestOptions options) { + final StorageRequest getRequest = + new StorageRequest(options, this.getStorageUri()) { + + @Override + public void setRequestLocationMode() { + this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY); + } + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) + throws Exception { + return FileRequest.getShareStats(share.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); + } + + @Override + public ShareStats preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + share.updatePropertiesFromResponse(this.getConnection()); + return null; + } + + @Override + public ShareStats postProcessResponse(HttpURLConnection connection, CloudFileShare share, + CloudFileClient client, OperationContext context, ShareStats shareStats) throws Exception { + return ShareStatsHandler.readShareStatsFromStream(connection.getInputStream()); + } + }; + + return getRequest; + } + /** * Returns a value that indicates whether the share exists. * @@ -559,7 +801,7 @@ private boolean exists(final boolean primaryOnly, final AccessCondition accessCo } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); return ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); @@ -572,8 +814,8 @@ private StorageRequest existsImpl(fina @Override public void setRequestLocationMode() { - this.setRequestLocationMode(primaryOnly ? RequestLocationMode.PRIMARY_ONLY - : RequestLocationMode.PRIMARY_OR_SECONDARY); + this.setRequestLocationMode( + primaryOnly ? RequestLocationMode.PRIMARY_ONLY : RequestLocationMode.PRIMARY_OR_SECONDARY); } @Override @@ -586,7 +828,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare sha @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -623,6 +865,52 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { } } + /** + * Returns a shared access signature for the share. Note this does not contain the leading "?". + * + * @param policy + * An {@link SharedAccessFilePolicy} object that represents the access policy for the + * shared access signature. + * @param groupPolicyIdentifier + * A String which represents the share-level access policy. + * + * @return A String which represents a shared access signature for the share. + * + * @throws StorageException + * If a storage service error occurred. + * @throws InvalidKeyException + * If the key is invalid. + */ + public String generateSharedAccessSignature(final SharedAccessFilePolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { + if (!StorageCredentialsHelper.canCredentialsSignRequest(this.fileServiceClient.getCredentials())) { + final String errorMessage = SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY; + throw new IllegalArgumentException(errorMessage); + } + + final String resourceName = this.getSharedAccessCanonicalName(); + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(policy, + null /* SharedAccessHeaders */, groupPolicyIdentifier, resourceName, this.fileServiceClient); + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(policy, + null /* SharedAccessHeaders */, groupPolicyIdentifier, "s", signature); + + return builder.toString(); + } + + /** + * Returns the canonical name for shared access. + * + * @return the canonical name for shared access. + */ + private String getSharedAccessCanonicalName() { + String accountName = this.getServiceClient().getCredentials().getAccountName(); + String shareName = this.getName(); + + return String.format("/%s/%s/%s", SR.FILE, accountName, shareName); + } + /** * Uploads the share's metadata. * @@ -659,13 +947,12 @@ public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions o } opContext.initialize(); - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } - @DoesServiceRequest private StorageRequest uploadMetadataImpl( final AccessCondition accessCondition, final FileRequestOptions options) { final StorageRequest putRequest = new StorageRequest( @@ -686,7 +973,90 @@ public void setHeaders(HttpURLConnection connection, CloudFileShare share, Opera @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + share.updatePropertiesFromResponse(this.getConnection()); + return null; + } + }; + + return putRequest; + } + + /** + * Updates the share's properties on the storage service. + *

    + * Use {@link CloudFileShare#downloadAttributes} to retrieve the latest values for + * the share's properties and metadata from the Microsoft Azure storage service. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void uploadProperties() throws StorageException { + this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */); + } + + /** + * Updates the share's properties using the request options, and operation context. + *

    + * Use {@link CloudFileShare#downloadAttributes} to retrieve the latest values for + * the share's properties and metadata from the Microsoft Azure storage service. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void uploadProperties( + AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + this.uploadPropertiesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest uploadPropertiesImpl( + final AccessCondition accessCondition, final FileRequestOptions options) { + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) + throws Exception { + return FileRequest.setShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, accessCondition, share.properties); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -694,6 +1064,7 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope throws Exception { if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { this.setNonExceptionedRetryableFailure(true); + return null; } share.updatePropertiesFromResponse(this.getConnection()); @@ -703,6 +1074,110 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope return putRequest; } + + /** + * Uploads the share's permissions. + * + * @param permissions + * A {@link FileSharePermissions} object that represents the permissions to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final FileSharePermissions permissions) throws StorageException { + this.uploadPermissions(permissions, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Uploads the share's permissions using the specified request options and operation context. + * + * @param permissions + * A {@link FileSharePermissions} object that represents the permissions to upload. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final FileSharePermissions permissions, final AccessCondition accessCondition, + FileRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, + uploadPermissionsImpl(permissions, accessCondition, options), options.getRetryPolicyFactory(), + opContext); + } + + private StorageRequest uploadPermissionsImpl( + final FileSharePermissions permissions, final AccessCondition accessCondition, + final FileRequestOptions options) throws StorageException { + try { + final StringWriter outBuffer = new StringWriter(); + SharedAccessPolicySerializer.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(), + outBuffer); + final byte[] aclBytes = outBuffer.toString().getBytes(Constants.UTF8_CHARSET); + final StorageRequest putRequest = + new StorageRequest(options, this.getStorageUri()) { + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, + OperationContext context) throws Exception { + this.setSendStream(new ByteArrayInputStream(aclBytes)); + this.setLength((long) aclBytes.length); + return FileRequest.setAcl(share.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, accessCondition); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, context); + } + + @Override + public Void preProcessResponse(CloudFileShare share, CloudFileClient client, + OperationContext context) throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + share.updatePropertiesFromResponse(this.getConnection()); + return null; + } + }; + + return putRequest; + } + catch (final IllegalArgumentException e) { + StorageException translatedException = StorageException.translateException(null, e, null); + throw translatedException; + } + catch (final XMLStreamException e) { + // The request was not even made. There was an error while trying to read the permissions. Just throw. + StorageException translatedException = StorageException.translateException(null, e, null); + throw translatedException; + } + catch (UnsupportedEncodingException e) { + // The request was not even made. There was an error while trying to read the permissions. Just throw. + StorageException translatedException = StorageException.translateException(null, e, null); + throw translatedException; + } + } /** * Returns a reference to a {@link CloudFileDirectory} object that represents the root file directory within this @@ -712,36 +1187,47 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope * @throws StorageException * @throws URISyntaxException */ + @SuppressWarnings("deprecation") public CloudFileDirectory getRootDirectoryReference() throws StorageException, URISyntaxException { return new CloudFileDirectory(this.storageUri, this.fileServiceClient); } /** - * Strips the query and verifies the URI is absolute. + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. * * @param completeUri * A {@link StorageUri} object which represents the complete URI. - * @param existingClient - * A {@link CloudFileClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise, false. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - private void parseQueryAndVerify(final StorageUri completeUri, final CloudFileClient existingClient, - final boolean usePathStyleUri) throws StorageException, URISyntaxException { - Utility.assertNotNull("completeUri", completeUri); + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { + Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - this.fileServiceClient = (existingClient == null) ? new CloudFileClient( - PathUtility.getServiceClientBaseAddress(this.storageUri, usePathStyleUri), null) : existingClient; - this.name = PathUtility.getShareNameFromUri(completeUri.getPrimaryUri(), usePathStyleUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); + + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); + } + + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.fileServiceClient = new CloudFileClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getShareNameFromUri(this.storageUri.getPrimaryUri(), usePathStyleUris); + } + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); + } } /** @@ -781,8 +1267,8 @@ public String getName() { } /** - * Returns the metadata for the share. This value is initialized with the metadata from the queue by a call to - * {@link #downloadAttributes}, and is set on the queue with a call to {@link #uploadMetadata}. + * Returns the metadata for the share. This value is initialized with the metadata from the share by a call to + * {@link #downloadAttributes}, and is set on the share with a call to {@link #uploadMetadata}. * * @return A java.util.HashMap object that represents the metadata for the share. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyState.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyState.java new file mode 100644 index 0000000000000..6fe3905cce2bf --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyState.java @@ -0,0 +1,193 @@ +/** + * Copyright 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.azure.storage.file; + +import java.net.URI; +import java.util.Date; + +/** + * Represents the attributes of a copy operation. + */ +public final class CopyState { + /** + * Holds the ID for the copy operation. + */ + private String copyId; + + /** + * Holds the time the copy operation completed, whether completion was due to a successful copy, an abort, or a + * failure. + */ + private Date completionTime; + + /** + * Holds the status of the copy operation. + */ + private CopyStatus status; + + /** + * Holds the source URI of a copy operation. + */ + private URI source; + + /** + * Holds the number of bytes copied in the operation so far. + */ + private Long bytesCopied; + + /** + * Holds the total number of bytes in the source of the copy. + */ + private Long totalBytes; + + /** + * Holds the description of the current status. + */ + private String statusDescription; + + /** + * Gets the ID of the copy operation. + * + * @return A String which represents the ID of the copy operation. + */ + public String getCopyId() { + return this.copyId; + } + + /** + * Gets the time that the copy operation completed. + * + * @return A {@link java.util.Date} object which represents the time that the copy operation completed. + */ + public Date getCompletionTime() { + return this.completionTime; + } + + /** + * Gets the status of the copy operation. + * + * @return A {@link CopyStatus} object representing the status of the copy operation. + */ + public CopyStatus getStatus() { + return this.status; + } + + /** + * Gets the source URI of the copy operation. + * + * @return A {@link java.net.URI} object which represents the source URI of the copy operation in a string. + */ + public URI getSource() { + return this.source; + } + + /** + * Gets the number of bytes copied in the operation so far. + * + * @return A long which represents the number of bytes copied. + */ + public Long getBytesCopied() { + return this.bytesCopied; + } + + /** + * Gets the number of bytes total number of bytes to copy. + * + * @return A long which represents the total number of bytes to copy/ + */ + public Long getTotalBytes() { + return this.totalBytes; + } + + /** + * Gets the status description of the copy operation. + * + * @return A String which represents the status description. + */ + public String getStatusDescription() { + return this.statusDescription; + } + + /** + * Sets the ID of the copy operation. + * + * @param copyId + * A String which specifies the ID of the copy operation to set. + * + */ + void setCopyId(final String copyId) { + this.copyId = copyId; + } + + /** + * Sets the time that the copy operation completed. + * + * @param completionTime + * A {@link java.util.Date} object which specifies the time when the copy operation completed. + */ + void setCompletionTime(final Date completionTime) { + this.completionTime = completionTime; + } + + /** + * Sets the status of the copy operation. + * + * @param status + * A {@link CopyStatus} object specifies the status of the copy operation. + */ + void setStatus(final CopyStatus status) { + this.status = status; + } + + /** + * Sets the source URI of the copy operation. + * + * @param source + * A {@link java.net.URI} object which specifies the source URI. + */ + void setSource(final URI source) { + this.source = source; + } + + /** + * Sets the number of bytes copied so far. + * + * @param bytesCopied + * A long which specifies the number of bytes copied. + */ + void setBytesCopied(final Long bytesCopied) { + this.bytesCopied = bytesCopied; + } + + /** + * Sets the total number of bytes in the source to copy. + * + * @param totalBytes + * A long which specifies the number of bytes to copy. + */ + void setTotalBytes(final Long totalBytes) { + this.totalBytes = totalBytes; + } + + /** + * Sets the current status of the copy operation. + * + * @param statusDescription + * A String which specifies the status description. + */ + void setStatusDescription(final String statusDescription) { + this.statusDescription = statusDescription; + } +} diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyStatus.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyStatus.java new file mode 100644 index 0000000000000..4a874d8052237 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CopyStatus.java @@ -0,0 +1,86 @@ +/** + * Copyright 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.azure.storage.file; + +import java.util.Locale; + +import com.microsoft.azure.storage.core.Utility; + +/** + * Represents the status of a copy file operation. + */ +public enum CopyStatus { + /** + * The copy status is not specified. + */ + UNSPECIFIED, + + /** + * The copy status is invalid. + */ + INVALID, + + /** + * The copy operation is pending. + */ + PENDING, + + /** + * The copy operation succeeded. + */ + SUCCESS, + + /** + * The copy operation has been aborted. + */ + ABORTED, + + /** + * The copy operation encountered an error. + */ + FAILED; + + /** + * Parses a copy status from the given string. + * + * @param typeString + * A String that represents the string to parse. + * + * @return A CopyStatus value that represents the copy status. + */ + static CopyStatus parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("invalid".equals(typeString.toLowerCase(Locale.US))) { + return INVALID; + } + else if ("pending".equals(typeString.toLowerCase(Locale.US))) { + return PENDING; + } + else if ("success".equals(typeString.toLowerCase(Locale.US))) { + return SUCCESS; + } + else if ("aborted".equals(typeString.toLowerCase(Locale.US))) { + return ABORTED; + } + else if ("failed".equals(typeString.toLowerCase(Locale.US))) { + return FAILED; + } + else { + return UNSPECIFIED; + } + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileConstants.java index 58e30e2f163ae..815c8c28ca416 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileConstants.java @@ -46,6 +46,11 @@ final class FileConstants { */ public static final String SHARES_ELEMENT = "Shares"; + /** + * XML element for share quota. + */ + public static final String SHARE_QUOTA_ELEMENT = "Quota"; + /** * XML element for file range start elements. */ @@ -56,6 +61,11 @@ final class FileConstants { */ public static final int DEFAULT_CONCURRENT_REQUEST_COUNT = 1; + /** + * The largest possible share quota in GB. + */ + public static final int MAX_SHARE_QUOTA = 5120; + /** * The header that specifies file cache control. */ @@ -106,6 +116,11 @@ final class FileConstants { */ public static final String FILE_RANGE_WRITE = Constants.PREFIX_FOR_STORAGE_HEADER + "write"; + /** + * The header for the share quota. + */ + public static final String SHARE_QUOTA_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "share-quota"; + /** * Private Default Constructor. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryAttributes.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryAttributes.java new file mode 100644 index 0000000000000..d02ce17345f75 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryAttributes.java @@ -0,0 +1,138 @@ +/** + * Copyright 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.azure.storage.file; + +import java.net.URI; +import java.util.HashMap; + +import com.microsoft.azure.storage.StorageUri; + +/** + * RESERVED FOR INTERNAL USE. Represents a directory's attributes, including its properties and metadata. + */ +final class FileDirectoryAttributes { + /** + * Holds the directory's metadata. + */ + private HashMap metadata; + + /** + * Holds the directory's properties. + */ + private FileDirectoryProperties properties; + + /** + * Holds the name of the directory. + */ + private String name; + + /** + * Holds the list of URIs for all locations. + */ + private StorageUri storageUri; + + /** + * Initializes a new instance of the FileDirectoryAttributes class + */ + public FileDirectoryAttributes() { + this.setMetadata(new HashMap()); + this.setProperties(new FileDirectoryProperties()); + } + + /** + * Gets the metadata for the directory. + * + * @return A java.util.HashMap object containing the metadata for the directory. + */ + public HashMap getMetadata() { + return this.metadata; + } + + /** + * Gets the name of the directory. + * + * @return A String that represents name of the directory. + */ + public String getName() { + return this.name; + } + + /** + * Gets the properties for the directory. + * + * @return A FileDirectoryProperties object that represents the directory properties. + */ + public FileDirectoryProperties getProperties() { + return this.properties; + } + + /** + * Gets the list of URIs for all locations for the directory. + * + * @return A {@link StorageUri} object that represents the list of URIs for all locations for the directory. + */ + public final StorageUri getStorageUri() { + return this.storageUri; + } + + /** + * Gets the URI of the directory. + * + * @return A java.net.URI object that represents the URI of the directory. + */ + public URI getUri() { + return this.storageUri.getPrimaryUri(); + } + + /** + * Sets the metadata for a directory. + * + * @param metadata + * The metadata to set. + */ + public void setMetadata(final HashMap metadata) { + this.metadata = metadata; + } + + /** + * Sets the name of the directory. + * + * @param name + * A String that represents name of the directory. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * Sets the properties for a directory. + * + * @param properties + * The directory properties to set. + */ + public void setProperties(final FileDirectoryProperties properties) { + this.properties = properties; + } + + /** + * Sets the list of URIs for all locations for the directory. + * + * @param storageUri + * The list of URIs for all locations for the directory. + */ + protected void setStorageUri(final StorageUri storageUri) { + this.storageUri = storageUri; + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java index 84c4b5271888d..c6dcfd3cc8038 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java @@ -39,8 +39,9 @@ public final class FileDirectoryProperties { * The ETag value is a unique identifier that is updated when a write operation is performed against the directory. * It may be used to perform operations conditionally, providing concurrency control and improved efficiency. *

    - * The {@link AccessCondition#ifMatch} and {@link AccessCondition#ifNoneMatch} methods take an ETag value and return - * an {@link AccessCondition} object that may be specified on the request. + * The {@link AccessCondition#generateIfMatchCondition(String)} and + * {@link AccessCondition#generateIfNoneMatchCondition(String)} methods take an ETag value and return an + * {@link AccessCondition} object that may be specified on the request. * * @return A String which represents the ETag. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java index f6dd0dfe5baa2..f0ded017f865d 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java @@ -55,6 +55,11 @@ public final class FileProperties { * returns null. */ private String contentType; + + /** + * Represents the state of the most recent or pending copy operation. + */ + private CopyState copyState; /** * Represents the size, in bytes, of the file. @@ -154,15 +159,25 @@ public String getContentMD5() { public String getContentType() { return this.contentType; } - + + /** + * Gets the file's copy state. + * + * @return A {@link CopyState} object which represents the copy state of the file. + */ + public CopyState getCopyState() { + return this.copyState; + } + /** * Gets the ETag value for the file. *

    * The ETag value is a unique identifier that is updated when a write operation is performed against the file. It * may be used to perform operations conditionally, providing concurrency control and improved efficiency. *

    - * The {@link AccessCondition#ifMatch} and {@link AccessCondition#ifNoneMatch} methods take an ETag value and return - * an {@link AccessCondition} object that may be specified on the request. + * The {@link AccessCondition#generateIfMatchCondition(String)} and + * {@link AccessCondition#generateIfNoneMatchCondition(String)} methods take an ETag value and return an + * {@link AccessCondition} object that may be specified on the request. * * @return A String which represents the ETag value. */ @@ -247,6 +262,16 @@ public void setContentMD5(final String contentMD5) { public void setContentType(final String contentType) { this.contentType = contentType; } + + /** + * Sets the copy state value for the file. + * + * @param copyState + * A {@link CopyState} object which specifies the copy state value to set. + */ + protected void setCopyState(final CopyState copyState) { + this.copyState = copyState; + } /** * Sets the ETag value for the file. diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java index f440198441f0b..0559349e0015d 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java @@ -18,7 +18,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; +import java.util.Map; import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.Constants; @@ -38,6 +38,55 @@ final class FileRequest { private static final String RANGE_LIST_QUERY_ELEMENT_NAME = "rangelist"; + /** + * Generates a web request to abort a copy operation. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * The access condition to apply to the request. Only lease conditions are supported for this operation. + * @param copyId + * A String object that identifying the copy operation. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * An exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public static HttpURLConnection abortCopy(final URI uri, final FileRequestOptions fileOptions, + final OperationContext opContext, final AccessCondition accessCondition, final String copyId) + throws StorageException, IOException, URISyntaxException { + + final UriQueryBuilder builder = new UriQueryBuilder(); + + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.COPY); + builder.add(Constants.QueryConstants.COPY_ID, copyId); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + + request.setRequestProperty(Constants.HeaderConstants.COPY_ACTION_HEADER, + Constants.HeaderConstants.COPY_ACTION_ABORT); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + } + + return request; + } + /** * Adds the metadata. * @@ -46,7 +95,7 @@ final class FileRequest { * @param metadata * The metadata. */ - public static void addMetadata(final HttpURLConnection request, final HashMap metadata, + public static void addMetadata(final HttpURLConnection request, final Map metadata, final OperationContext opContext) { BaseRequest.addMetadata(request, metadata, opContext); } @@ -55,9 +104,9 @@ public static void addMetadata(final HttpURLConnection request, final HashMapjava.net.URI object that specifies the absolute URI. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param source + * The canonical path to the source file, + * in the form ////. + * @param sourceAccessConditionType + * A type of condition to check on the source file. + * @param sourceAccessConditionValue + * The value of the condition to check on the source file + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public static HttpURLConnection copyFrom(final URI uri, final FileRequestOptions fileOptions, + final OperationContext opContext, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, String source) + throws StorageException, IOException, URISyntaxException { + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, null, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + + request.setRequestProperty(Constants.HeaderConstants.COPY_SOURCE_HEADER, source); + + if (sourceAccessCondition != null) { + sourceAccessCondition.applyConditionToRequest(request); + } + + if (destinationAccessCondition != null) { + destinationAccessCondition.applyConditionToRequest(request); + } + + return request; + } + + /** + * Adds the properties. + * + * @param request + * The request + * @param properties + * The share properties + */ + private static void addProperties(final HttpURLConnection request, FileShareProperties properties) { + final Integer shareQuota = properties.getShareQuota(); + BaseRequest.addOptionalHeader( + request, FileConstants.SHARE_QUOTA_HEADER, shareQuota == null ? null : shareQuota.toString()); + } + /** * Constructs a web request to create a new share. Sign with 0 length. * @@ -82,14 +196,19 @@ private static void addProperties(final HttpURLConnection request, FilePropertie * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. + * @param properties + * The properties to set for the share. * @return a HttpURLConnection configured for the operation. * @throws StorageException * @throws IllegalArgumentException */ public static HttpURLConnection createShare(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + final OperationContext opContext, final FileShareProperties properties) + throws IOException, URISyntaxException, StorageException { final UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); - return BaseRequest.create(uri, fileOptions, shareBuilder, opContext); + final HttpURLConnection request = BaseRequest.create(uri, fileOptions, shareBuilder, opContext); + addProperties(request, properties); + return request; } /** @@ -160,6 +279,41 @@ public static HttpURLConnection deleteShare(final URI uri, final FileRequestOpti return request; } + /** + * Constructs a web request to return the ACL for this share. Sign with no length specified. + * + * @param uri + * The absolute URI to the share. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getAcl(final URI uri, final FileRequestOptions fileOptions, + final AccessCondition accessCondition, final OperationContext opContext) throws IOException, + URISyntaxException, StorageException { + final UriQueryBuilder builder = getShareUriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); + + request.setRequestMethod(Constants.HTTP_GET); + + if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { + accessCondition.applyLeaseConditionToRequest(request); + } + + return request; + } + /** * Constructs a HttpURLConnection to download the file, Sign with no length specified. * @@ -325,6 +479,35 @@ public static HttpURLConnection getShareProperties(final URI uri, final FileRequ return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder); } + /** + * Constructs a web request to return the stats, such as usage, for this share. Sign with no length specified. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param options + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getShareStats(final URI uri, final FileRequestOptions options, + final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); + shareBuilder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.STATS); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(uri, options, shareBuilder, opContext); + retConnection.setRequestMethod(Constants.HTTP_GET); + + return retConnection; + } + /** * Gets the share Uri query builder. * @@ -431,7 +614,7 @@ public static HttpURLConnection listShares(final URI uri, final FileRequestOptio return request; } - + /** * Constructs a web request to set user-defined metadata for the share, Sign with 0 Length. * @@ -457,6 +640,36 @@ public static HttpURLConnection setShareMetadata(final URI uri, final FileReques return setMetadata(uri, fileOptions, opContext, accessCondition, shareBuilder); } + /** + * Constructs a web request to set user-defined metadata for the directory, Sign with 0 Length. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the directory. + * @return a HttpURLConnection configured for the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * */ + public static HttpURLConnection setDirectoryMetadata(final URI uri, final FileRequestOptions fileOptions, + final OperationContext opContext, final AccessCondition accessCondition) throws IOException, + URISyntaxException, StorageException { + final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder(); + return setMetadata(uri, fileOptions, opContext, accessCondition, directoryBuilder); + } + /** * Constructs a web request to create a new directory. Sign with 0 length. * @@ -693,6 +906,55 @@ public static HttpURLConnection putRange(final URI uri, final FileRequestOptions return request; } + /** + * Constructs a HttpURLConnection to set the share's properties, signed with zero length specified. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param options + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the file. + * @param properties + * The properties to upload. + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + */ + public static HttpURLConnection setShareProperties(final URI uri, final FileRequestOptions options, + final OperationContext opContext, final AccessCondition accessCondition, final FileShareProperties properties) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = getShareUriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, options, builder, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + } + + if (properties != null) { + addProperties(request, properties); + } + + return request; + } + /** * Constructs a HttpURLConnection to set the file's size, Sign with zero length specified. * @@ -731,6 +993,42 @@ public static HttpURLConnection resize(final URI uri, final FileRequestOptions f return request; } + /** + * Sets the ACL for the share. Sign with length of aclBytes. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * */ + public static HttpURLConnection setAcl(final URI uri, final FileRequestOptions fileOptions, + final OperationContext opContext, final AccessCondition accessCondition) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = getShareUriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); + + request.setRequestMethod(Constants.HTTP_PUT); + request.setDoOutput(true); + + if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { + accessCondition.applyLeaseConditionToRequest(request); + } + + return request; + } + /** * Constructs a HttpURLConnection to set metadata, Sign with 0 length. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java index b1baa806f2467..37497fa5d1434 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java @@ -89,9 +89,9 @@ public FileRequestOptions(final FileRequestOptions other) { * {@link #concurrentRequestCount} field's value is null, it will be set to the value specified by the * cloud file client's {@link CloudFileClient#getConcurrentRequestCount} method. */ - protected static final FileRequestOptions applyDefaults(final FileRequestOptions options, + protected static final FileRequestOptions populateAndApplyDefaults(final FileRequestOptions options, final CloudFileClient client) { - return FileRequestOptions.applyDefaults(options, client, true); + return FileRequestOptions.populateAndApplyDefaults(options, client, true); } /** @@ -108,15 +108,21 @@ protected static final FileRequestOptions applyDefaults(final FileRequestOptions * @param setStartTime * whether to initialize the startTimeInMs field, or not */ - protected static final FileRequestOptions applyDefaults(final FileRequestOptions options, + protected static final FileRequestOptions populateAndApplyDefaults(final FileRequestOptions options, final CloudFileClient client, final boolean setStartTime) { FileRequestOptions modifiedOptions = new FileRequestOptions(options); FileRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), setStartTime); - return FileRequestOptions.applyDefaultsInternal(modifiedOptions, client); + FileRequestOptions.applyDefaults(modifiedOptions); + return modifiedOptions; } - private static final FileRequestOptions applyDefaultsInternal(final FileRequestOptions modifiedOptions, - final CloudFileClient client) { + /** + * Applies defaults to the options passed in. + * + * @param modifiedOptions + * The options to apply defaults to. + */ + protected static void applyDefaults(final FileRequestOptions modifiedOptions) { Utility.assertNotNull("modifiedOptions", modifiedOptions); RequestOptions.applyBaseDefaultsInternal(modifiedOptions); if (modifiedOptions.getConcurrentRequestCount() == null) { @@ -134,21 +140,17 @@ private static final FileRequestOptions applyDefaultsInternal(final FileRequestO if (modifiedOptions.getDisableContentMD5Validation() == null) { modifiedOptions.setDisableContentMD5Validation(false); } - - return modifiedOptions; } /** * Populates any null fields in the first requestOptions object with values from the second requestOptions object. */ - private static final FileRequestOptions populateRequestOptions(FileRequestOptions modifiedOptions, + private static void populateRequestOptions(FileRequestOptions modifiedOptions, final FileRequestOptions clientOptions, boolean setStartTime) { RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, setStartTime); if (modifiedOptions.getConcurrentRequestCount() == null) { modifiedOptions.setConcurrentRequestCount(clientOptions.getConcurrentRequestCount()); } - - return modifiedOptions; } /** @@ -198,7 +200,7 @@ public Boolean getDisableContentMD5Validation() { *

    * The default concurrent request count is set in the client and is by default 1, indicating no concurrency. You can * change the concurrent request count on this request by setting this property. You can also change the value on - * the {@link FileServiceConstants#getDefaultRequestOptions()} object so that all subsequent requests made via the + * the {@link CloudFileClient#getDefaultRequestOptions()} object so that all subsequent requests made via the * service client will use that concurrent request count. * * @param concurrentRequestCount @@ -213,7 +215,7 @@ public void setConcurrentRequestCount(final Integer concurrentRequestCount) { *

    * The default useTransactionalContentMD5 value is set in the client and is by default false. You can * change the useTransactionalContentMD5 value on this request by setting this property. You can also change the - * value on the {@link FileServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via + * value on the {@link CloudFileClient#getDefaultRequestOptions()} object so that all subsequent requests made via * the service client will use that useTransactionalContentMD5 value. * * @param useTransactionalContentMD5 @@ -228,7 +230,7 @@ public void setUseTransactionalContentMD5(final Boolean useTransactionalContentM *

    * The default storeFileContentMD5 value is set in the client and is by default true. You can change * the storeFileContentMD5 value on this request by setting this property. You can also change the value on the - * {@link FileServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service + * {@link CloudFileClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service * client will use that storeFileContentMD5 value. * * @param storeFileContentMD5 @@ -243,7 +245,7 @@ public void setStoreFileContentMD5(final Boolean storeFileContentMD5) { *

    * The default disableContentMD5Validation value is set in the client and is by default false. You can * change the disableContentMD5Validation value on this request by setting this property. You can also change the - * value on the {@link FileServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via + * value on the {@link CloudFileClient#getDefaultRequestOptions()} object so that all subsequent requests made via * the service client will use that disableContentMD5Validation value. * * @param disableContentMD5Validation diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java index c5cb4610eb22c..4ed960a1932be 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java @@ -17,6 +17,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.text.ParseException; import java.util.Calendar; import java.util.Date; @@ -31,13 +32,56 @@ * RESERVED FOR INTERNAL USE. A class used to parse the response from file, directory, and share operations. */ final class FileResponse extends BaseResponse { + /** + * Gets the copyState + * + * @param request + * The response from server. + * @return The CopyState. + * @throws URISyntaxException + * @throws ParseException + */ + public static CopyState getCopyState(final HttpURLConnection request) throws URISyntaxException, ParseException { + String copyStatusString = request.getHeaderField(Constants.HeaderConstants.COPY_STATUS); + if (!Utility.isNullOrEmpty(copyStatusString)) { + final CopyState copyState = new CopyState(); + + copyState.setStatus(CopyStatus.parse(copyStatusString)); + copyState.setCopyId(request.getHeaderField(Constants.HeaderConstants.COPY_ID)); + copyState.setStatusDescription(request.getHeaderField(Constants.HeaderConstants.COPY_STATUS_DESCRIPTION)); + + final String copyProgressString = request.getHeaderField(Constants.HeaderConstants.COPY_PROGRESS); + if (!Utility.isNullOrEmpty(copyProgressString)) { + String[] progressSequence = copyProgressString.split("/"); + copyState.setBytesCopied(Long.parseLong(progressSequence[0])); + copyState.setTotalBytes(Long.parseLong(progressSequence[1])); + } + + final String copySourceString = request.getHeaderField(Constants.HeaderConstants.COPY_SOURCE); + if (!Utility.isNullOrEmpty(copySourceString)) { + copyState.setSource(new URI(copySourceString)); + } + + final String copyCompletionTimeString = + request.getHeaderField(Constants.HeaderConstants.COPY_COMPLETION_TIME); + if (!Utility.isNullOrEmpty(copyCompletionTimeString)) { + copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(copyCompletionTimeString)); + } + + return copyState; + } + else { + return null; + } + } + /** * Gets the FileShareAttributes from the given request. * * @param request - * the request to get attributes from. + * the request to get attributes from * @param usePathStyleUris - * a value indicating if the account is using pathSytleUris. + * a value indicating if the account is using pathSytleUris * @return the FileShareAttributes from the given request. * @throws StorageException */ @@ -57,6 +101,7 @@ public static FileShareAttributes getFileShareAttributes(final HttpURLConnection final FileShareProperties shareProperties = shareAttributes.getProperties(); shareProperties.setEtag(BaseResponse.getEtag(request)); + shareProperties.setShareQuota(parseShareQuota(request)); shareProperties.setLastModified(new Date(request.getLastModified())); shareAttributes.setMetadata(getMetadata(request)); @@ -64,18 +109,35 @@ public static FileShareAttributes getFileShareAttributes(final HttpURLConnection } /** - * Gets the FileDirectoryProperties from the given request. + * Gets the FileDirectoryAttributes from the given request. * * @param request - * the request to get properties from. - * @return the FileDirectoryProperties from the given request. + * the request to get attributes from. + * @param usePathStyleUris + * a value indicating if the account is using pathSytleUris. + * @return the FileDirectoryAttributes from the given request. + * @throws StorageException */ - public static FileDirectoryProperties getFileDirectoryProperties(final HttpURLConnection request) { - final FileDirectoryProperties directoryProperties = new FileDirectoryProperties(); + public static FileDirectoryAttributes getFileDirectoryAttributes(final HttpURLConnection request, + final boolean usePathStyleUris) throws StorageException { + final FileDirectoryAttributes directoryAttributes = new FileDirectoryAttributes(); + URI tempURI; + try { + tempURI = PathUtility.stripSingleURIQueryAndFragment(request.getURL().toURI()); + } + catch (final URISyntaxException e) { + final StorageException wrappedUnexpectedException = Utility.generateNewUnexpectedStorageException(e); + throw wrappedUnexpectedException; + } + + directoryAttributes.setName(PathUtility.getDirectoryNameFromURI(tempURI, usePathStyleUris)); + + final FileDirectoryProperties directoryProperties = directoryAttributes.getProperties(); directoryProperties.setEtag(BaseResponse.getEtag(request)); directoryProperties.setLastModified(new Date(request.getLastModified())); + directoryAttributes.setMetadata(getMetadata(request)); - return directoryProperties; + return directoryAttributes; } /** @@ -87,8 +149,11 @@ public static FileDirectoryProperties getFileDirectoryProperties(final HttpURLCo * The file uri to set. * * @return the CloudFileAttributes from the given request + * @throws ParseException + * @throws URISyntaxException */ - public static FileAttributes getFileAttributes(final HttpURLConnection request, final StorageUri resourceURI) { + public static FileAttributes getFileAttributes(final HttpURLConnection request, final StorageUri resourceURI) + throws URISyntaxException, ParseException { final FileAttributes fileAttributes = new FileAttributes(); final FileProperties properties = fileAttributes.getProperties(); @@ -99,6 +164,7 @@ public static FileAttributes getFileAttributes(final HttpURLConnection request, properties.setContentMD5(request.getHeaderField(Constants.HeaderConstants.CONTENT_MD5)); properties.setContentType(request.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE)); properties.setEtag(BaseResponse.getEtag(request)); + properties.setCopyState(FileResponse.getCopyState(request)); final Calendar lastModifiedCalendar = Calendar.getInstance(Utility.LOCALE_US); lastModifiedCalendar.setTimeZone(Utility.UTC_ZONE); @@ -129,4 +195,23 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { return fileAttributes; } + + /** + * Parses out the share quota value from a java.net.HttpURLConnection. + * + * @param request + * the request to get attributes from + * @return the share quota (in GB) or null if none is specified + */ + static Integer parseShareQuota(final HttpURLConnection request) { + Integer shareQuota = request.getHeaderFieldInt(FileConstants.SHARE_QUOTA_HEADER, -1); + return (shareQuota == -1) ? null : shareQuota; + } + + /** + * Private Default Ctor + */ + private FileResponse() { + super(); + } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileServiceProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileServiceProperties.java new file mode 100644 index 0000000000000..1bd71267051f9 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileServiceProperties.java @@ -0,0 +1,74 @@ +/** + * Copyright 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.azure.storage.file; + +import com.microsoft.azure.storage.CorsProperties; +import com.microsoft.azure.storage.ServiceProperties; + +/** + * Class representing a set of properties pertaining to the Azure File service. + */ +public class FileServiceProperties { + private ServiceProperties serviceProperties; + + /** + * Generate a FileServiceProperties from a new ServiceProperties. + */ + public FileServiceProperties() { + this(new ServiceProperties()); + } + + /** + * Generate a FileServiceProperties from the given ServiceProperties. + * + * @param properties + * The ServiceProperties to use + */ + FileServiceProperties(ServiceProperties properties) { + serviceProperties = properties; + serviceProperties.setHourMetrics(null); + serviceProperties.setMinuteMetrics(null); + serviceProperties.setLogging(null); + serviceProperties.setDefaultServiceVersion(null); + } + + /** + * Gets the Cross-Origin Resource Sharing (CORS) properties. + * + * @return A {@link CorsProperties} object which represents the CORS properties. + */ + public CorsProperties getCors() { + return serviceProperties.getCors(); + } + + /** + * Sets the Cross-Origin Resource Sharing (CORS) properties. + * + * @param cors + * A {@link CorsProperties} object which represents the CORS properties. + */ + public void setCors(CorsProperties cors) { + serviceProperties.setCors(cors); + } + + /** + * Gets the ServiceProperties for use by the service. + * + * @return The ServiceProperties + */ + ServiceProperties getServiceProperties() { + return serviceProperties; + } +} diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileSharePermissions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileSharePermissions.java new file mode 100644 index 0000000000000..91dbc710fabc5 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileSharePermissions.java @@ -0,0 +1,30 @@ +/** + * Copyright 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.azure.storage.file; + +import com.microsoft.azure.storage.Permissions; + +/** + * Represents the permissions for a share. + *

    + * The share's permissions encompass its access policies, represented by the {@link #getSharedAccessPolicies} method. + * This setting references a collection of shared access policies for the share. A shared access policy may + * be used to control the start time, expiry time, and permissions for one or more shared access signatures. + * A shared access signature provides delegated access to the share's resources. + * For more information on managing share permissions, see + * Managing Access to Shares and Files. + */ +public final class FileSharePermissions extends Permissions { +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java index 811ad475a1af4..c5e29c6e35744 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java @@ -17,6 +17,7 @@ import java.util.Date; import com.microsoft.azure.storage.AccessCondition; +import com.microsoft.azure.storage.core.Utility; /** * Represents the system properties for a share. @@ -33,14 +34,20 @@ public final class FileShareProperties { */ private Date lastModified; + /** + * Represents the limit on the size of files (in GB) stored on the share. + */ + private Integer shareQuota; + /** * Gets the ETag value of the share. *

    * The ETag value is a unique identifier that is updated when a write operation is performed against the share. It * may be used to perform operations conditionally, providing concurrency control and improved efficiency. *

    - * The {@link AccessCondition#ifMatch} and {@link AccessCondition#ifNoneMatch} methods take an ETag value and return - * an {@link AccessCondition} object that may be specified on the request. + * The {@link AccessCondition#generateIfMatchCondition(String)} and + * {@link AccessCondition#generateIfNoneMatchCondition(String)} methods take an ETag value and return an + * {@link AccessCondition} object that may be specified on the request. * * @return A String which represents the ETag. */ @@ -57,6 +64,16 @@ public Date getLastModified() { return this.lastModified; } + /** + * Gets the limit on the size of files (in GB) stored on the share. + * + * @return A java.lang.Integer object which represents the limit on + * the size of files stored on the share. + */ + public Integer getShareQuota() { + return shareQuota; + } + /** * Sets the ETag value on the share. * @@ -76,4 +93,18 @@ protected void setEtag(final String etag) { protected void setLastModified(final Date lastModified) { this.lastModified = lastModified; } + + /** + * Sets the limit on the size of files (in GB) stored on the share. + * + * @param shareQuota + * A java.lang.Integer object which represents the limit on + * the size of files stored on the share. + */ + public void setShareQuota(Integer shareQuota) { + if (shareQuota != null) { + Utility.assertInBounds("Share Quota", shareQuota, 1, FileConstants.MAX_SHARE_QUOTA); + } + this.shareQuota = shareQuota; + } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java index aa2b370952b8c..c73693da14e5e 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java @@ -159,12 +159,14 @@ public void characters(char ch[], int start, int length) throws SAXException { } private void getProperties(String currentNode, String value) throws ParseException { - if (currentNode.equals(Constants.LAST_MODIFIED_ELEMENT)) { this.attributes.getProperties().setLastModified(Utility.parseRFC1123DateFromStringInGMT(value)); } else if (currentNode.equals(Constants.ETAG_ELEMENT)) { this.attributes.getProperties().setEtag(Utility.formatETag(value)); } + else if (currentNode.equals(FileConstants.SHARE_QUOTA_ELEMENT)) { + this.attributes.getProperties().setShareQuota(Utility.isNullOrEmpty(value) ? null : Integer.parseInt(value)); + } } -} +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStats.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStats.java new file mode 100644 index 0000000000000..accd1b98a3912 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStats.java @@ -0,0 +1,44 @@ +/** + * Copyright 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.azure.storage.file; + +/** + * Class representing a set of statistics pertaining to a cloud file share. + */ +public final class ShareStats { + /** + * The approximate size of the data stored on the share, in GB. + */ + private int usage; + + /** + * Gets the approximate size of the data stored on the share, in GB. + * + * @return the share usage + */ + public int getUsage() { + return usage; + } + + /** + * Sets approximate size of the data stored on the share, in GB. + * + * @param usage + * The approximate size of the data stored on the share, in GB. + */ + void setUsage(int usage) { + this.usage = usage; + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStatsHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStatsHandler.java new file mode 100644 index 0000000000000..fc31e65ed0b86 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareStatsHandler.java @@ -0,0 +1,91 @@ +/** + * Copyright 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.azure.storage.file; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Stack; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to deserialize share stats. + */ +final class ShareStatsHandler extends DefaultHandler { + /** + * The name of the share usage XML element. + */ + private final static String USAGE_NAME = "ShareUsage"; + + private final Stack elementStack = new Stack(); + private StringBuilder builder = new StringBuilder(); + + private final ShareStats stats = new ShareStats(); + + /** + * Constructs a {@link ShareStats} object from an XML document received from the service. + * + * @param inStream + * The XMLStreamReader object. + * @return + * A {@link ShareStats} object containing the properties in the XML document. + * + * @throws SAXException + * @throws ParserConfigurationException + * @throws IOException + */ + public static ShareStats readShareStatsFromStream(final InputStream inStream) + throws ParserConfigurationException, SAXException, IOException { + SAXParser saxParser = Utility.getSAXParser(); + ShareStatsHandler handler = new ShareStatsHandler(); + saxParser.parse(inStream, handler); + + return handler.stats; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + this.elementStack.push(localName); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + String currentNode = this.elementStack.pop(); + + // if the node popped from the stack and the localName don't match, the xml document is improperly formatted + if (!localName.equals(currentNode)) { + throw new SAXException(SR.INVALID_RESPONSE_RECEIVED); + } + + if (USAGE_NAME.equals(currentNode)) { + this.stats.setUsage(Integer.parseInt(this.builder.toString())); + } + + this.builder = new StringBuilder(); + } + + @Override + public void characters(char ch[], int start, int length) throws SAXException { + this.builder.append(ch, start, length); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFileHeaders.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFileHeaders.java new file mode 100644 index 0000000000000..d8c0837503615 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFileHeaders.java @@ -0,0 +1,38 @@ +/** + * Copyright 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.azure.storage.file; + +import com.microsoft.azure.storage.SharedAccessHeaders; + +/** + * Represents the optional headers that can be returned with files accessed using SAS. + */ +public final class SharedAccessFileHeaders extends SharedAccessHeaders { + /** + * Initializes a new instance of the {@link SharedAccessFileHeaders} class. + */ + public SharedAccessFileHeaders() { + } + + /** + * Initializes a new instance of the {@link SharedAccessFileHeaders} class based on an existing instance. + * + * @param other + * A {@link SharedAccessHeaders} object which specifies the set of properties to clone. + */ + public SharedAccessFileHeaders(SharedAccessHeaders other) { + super(other); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePermissions.java similarity index 59% rename from microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java rename to microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePermissions.java index c97a82f34a918..414349b3873e1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePermissions.java @@ -12,22 +12,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.microsoft.azure.storage; +package com.microsoft.azure.storage.file; /** - * Specifies the authentication scheme that is used to sign HTTP requests. - * - * @deprecated as of 2.0.0. In the future, only SHAREDKEYFULL will be used. + * Specifies the set of possible permissions for a shared access policy. */ -@Deprecated -public enum AuthenticationScheme { +public enum SharedAccessFilePermissions { + /** + * Specifies Read access granted. + */ + READ, + + /** + * Specifies Write access granted. + */ + WRITE, + /** - * Signs HTTP requests using the Shared Key Lite authentication scheme. + * Specifies Delete access granted for files. */ - SHAREDKEYLITE, + DELETE, /** - * Signs HTTP requests using the Shared Key authentication scheme. + * Specifies List access granted. */ - SHAREDKEYFULL; -} + LIST; +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePolicy.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePolicy.java new file mode 100644 index 0000000000000..0e0468da99c60 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/SharedAccessFilePolicy.java @@ -0,0 +1,124 @@ +/** + * Copyright 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.azure.storage.file; + +import java.util.EnumSet; + +import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.SharedAccessPolicy; + +/** + * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access + * signature. + */ +public final class SharedAccessFilePolicy extends SharedAccessPolicy { + /** + * The permissions for a shared access signature associated with this shared access policy. + */ + private EnumSet permissions; + + /** + * Gets the permissions for a shared access signature associated with this shared access policy. + * + * @return A java.util.EnumSet object that contains {@link SharedAccessFilePermissions} values that + * represents the set of shared access permissions. + */ + public EnumSet getPermissions() { + return this.permissions; + } + + /** + * Sets the permissions for a shared access signature associated with this shared access policy. + * + * @param permissions + * The permissions, represented by a java.util.EnumSet object that contains + * {@link SharedAccessFilePermissions} values, to set for the shared access signature. + */ + public void setPermissions(final EnumSet permissions) { + this.permissions = permissions; + } + + /** + * Converts this policy's permissions to a string. + * + * @return A String that represents the shared access permissions in the "rwdl" format, + * which is described at {@link #setPermissionsFromString(String)}. + */ + @Override + public String permissionsToString() { + if (this.permissions == null) { + return Constants.EMPTY_STRING; + } + + // The service supports a fixed order => rwdl + final StringBuilder builder = new StringBuilder(); + + if (this.permissions.contains(SharedAccessFilePermissions.READ)) { + builder.append("r"); + } + + if (this.permissions.contains(SharedAccessFilePermissions.WRITE)) { + builder.append("w"); + } + + if (this.permissions.contains(SharedAccessFilePermissions.DELETE)) { + builder.append("d"); + } + + if (this.permissions.contains(SharedAccessFilePermissions.LIST)) { + builder.append("l"); + } + + return builder.toString(); + } + + /** + * Sets shared access permissions using the specified permissions string. + * + * @param value + * A String that represents the shared access permissions. The string must contain one or + * more of the following values. Note they must be lowercase, and the order that they are specified must + * be in the order of "rwdl". + *

      + *
    • d: Delete access.
    • + *
    • l: List access.
    • + *
    • r: Read access.
    • + *
    • w: Write access.
    • + *
    + */ + public void setPermissionsFromString(final String value) { + EnumSet initial = EnumSet.noneOf(SharedAccessFilePermissions.class); + for (final char c : value.toCharArray()) { + switch (c) { + case 'r': + initial.add(SharedAccessFilePermissions.READ); + break; + case 'w': + initial.add(SharedAccessFilePermissions.WRITE); + break; + case 'd': + initial.add(SharedAccessFilePermissions.DELETE); + break; + case 'l': + initial.add(SharedAccessFilePermissions.LIST); + break; + default: + throw new IllegalArgumentException("value"); + } + } + + this.permissions = initial; + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java index 40cea6c9f208e..5616154136482 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java @@ -33,6 +33,7 @@ import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.SharedAccessPolicyHandler; import com.microsoft.azure.storage.SharedAccessPolicySerializer; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; @@ -107,37 +108,66 @@ private static CloudQueueMessage getFirstOrNull(final IterableCloudQueue class using the specified queue URI. The queue URI must - * include a SAS token. + * Creates an instance of the CloudQueue class using the specified queue URI. The queue + * URI must include a SAS token. * * @param uri * A java.net.URI object that represents the absolute URI of the queue. * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudQueue(final URI uri) throws URISyntaxException, StorageException { + public CloudQueue(final URI uri) throws StorageException { this(new StorageUri(uri, null)); } /** - * Creates an instance of the CloudQueue class using the specified queue URI. The queue URI must - * include a SAS token. + * Creates an instance of the CloudQueue class using the specified queue StorageUri. The + * queue StorageUri must include a SAS token. * * @param uri * A StorageUri object that represents the absolute URI of the queue. * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudQueue(final StorageUri uri) throws URISyntaxException, StorageException { - this(uri, null /* client */); + public CloudQueue(final StorageUri uri) throws StorageException { + this(uri, (StorageCredentials)null); } + /** + * Creates an instance of the CloudQueue class using the specified queue URI and + * credentials. If the URI contains a SAS token, the credentials must be null. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the queue. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudQueue(final URI uri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(uri), credentials); + } + + /** + * Creates an instance of the CloudQueue class using the specified queue StorageUri and + * credentials. If the StorageUri contains a SAS token, the credentials must be null. + * + * @param uri + * A StorageUri object that represents the absolute URI of the queue. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudQueue(final StorageUri uri, final StorageCredentials credentials) throws StorageException { + this.shouldEncodeMessage = true; + this.parseQueryAndVerify(uri, credentials); + } + /** * Creates an instance of the CloudQueue class using the specified name and client. * @@ -154,23 +184,23 @@ public CloudQueue(final StorageUri uri) throws URISyntaxException, StorageExcept * @throws StorageException * If a storage service error occurred. * @see Naming Queues and Metadata + * @deprecated as of 3.0.0. Please use {@link CloudQueueClient#getQueueReference(String)} */ + @Deprecated public CloudQueue(final String queueName, final CloudQueueClient client) throws URISyntaxException, StorageException { Utility.assertNotNull("client", client); Utility.assertNotNull("queueName", queueName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), queueName); - this.name = queueName; this.queueServiceClient = client; this.shouldEncodeMessage = true; - - this.parseQueryAndVerify(this.storageUri, client, client.isUsePathStyleUris()); } /** - * Creates an instance of the CloudQueue class using the specified queue URI and client. + * Creates an instance of the CloudQueue class using the specified queue URI and client. If the + * URI contains a SAS token, the service client must be null. * * @param uri * A java.net.URI object that represents the absolute URI of the queue. @@ -182,7 +212,9 @@ public CloudQueue(final String queueName, final CloudQueueClient client) throws * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudQueue#CloudQueue(URI, StorageCredentials)} */ + @Deprecated public CloudQueue(final URI uri, final CloudQueueClient client) throws URISyntaxException, StorageException { this(new StorageUri(uri, null), client); } @@ -200,20 +232,17 @@ public CloudQueue(final URI uri, final CloudQueueClient client) throws URISyntax * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudQueue#CloudQueue(StorageUri, StorageCredentials)} */ + @Deprecated public CloudQueue(final StorageUri uri, final CloudQueueClient client) throws URISyntaxException, StorageException { - Utility.assertNotNull("storageUri", uri); - - this.storageUri = uri; - - boolean usePathStyleUris = client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) - : client.isUsePathStyleUris(); - - this.name = PathUtility.getQueueNameFromUri(uri.getPrimaryUri(), usePathStyleUris); - this.queueServiceClient = client; this.shouldEncodeMessage = true; - - this.parseQueryAndVerify(this.storageUri, client, usePathStyleUris); + this.parseQueryAndVerify(uri, client == null ? null : client.getCredentials()); + + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.queueServiceClient = client; + } } /** @@ -277,7 +306,7 @@ public void addMessage(final CloudQueueMessage message, final int timeToLiveInSe } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.addMessageImpl(message, realTimeToLiveInSeconds, initialVisibilityDelayInSeconds, options), @@ -308,7 +337,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, messageBytes.length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, messageBytes.length, context); } @Override @@ -365,7 +394,7 @@ public void clear(QueueRequestOptions options, OperationContext opContext) throw } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.clearImpl(options), options.getRetryPolicyFactory(), opContext); @@ -385,7 +414,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -435,7 +464,7 @@ public void create(QueueRequestOptions options, OperationContext opContext) thro } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.createImpl(options), options.getRetryPolicyFactory(), opContext); @@ -460,7 +489,7 @@ public void setHeaders(HttpURLConnection connection, CloudQueue queue, Operation @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -513,7 +542,7 @@ public boolean createIfNotExists() throws StorageException { */ @DoesServiceRequest public boolean createIfNotExists(QueueRequestOptions options, OperationContext opContext) throws StorageException { - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); boolean exists = this.exists(true, options, opContext); if (exists) { @@ -569,7 +598,7 @@ public void delete(QueueRequestOptions options, OperationContext opContext) thro } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.deleteImpl(options), options.getRetryPolicyFactory(), opContext); @@ -589,7 +618,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -640,7 +669,7 @@ public boolean deleteIfExists() throws StorageException { */ @DoesServiceRequest public boolean deleteIfExists(QueueRequestOptions options, OperationContext opContext) throws StorageException { - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); boolean exists = this.exists(true, options, opContext); if (exists) { @@ -709,7 +738,7 @@ public void deleteMessage(final CloudQueueMessage message, QueueRequestOptions o } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.deleteMessageImpl(message, options), options.getRetryPolicyFactory(), opContext); @@ -734,7 +763,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -785,7 +814,7 @@ public void downloadAttributes(QueueRequestOptions options, OperationContext opC } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.downloadAttributesImpl(options), options.getRetryPolicyFactory(), opContext); @@ -810,7 +839,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -874,7 +903,7 @@ private boolean exists(final boolean primaryOnly, QueueRequestOptions options, O } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.existsImpl(primaryOnly, options), options.getRetryPolicyFactory(), opContext); @@ -901,7 +930,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1111,7 +1140,7 @@ public Iterable peekMessages(final int numberOfMessages, Queu } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.peekMessagesImpl(numberOfMessages, options), options.getRetryPolicyFactory(), opContext); @@ -1138,7 +1167,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1259,7 +1288,7 @@ public Iterable retrieveMessages(final int numberOfMessages, } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.retrieveMessagesImpl(numberOfMessages, visibilityTimeoutInSeconds, options), @@ -1282,7 +1311,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1324,16 +1353,6 @@ public void setShouldEncodeMessage(final boolean shouldEncodeMessage) { this.shouldEncodeMessage = shouldEncodeMessage; } - /** - * Sets the name of the queue. - * - * @param name - * A String that represents the name being assigned to the queue. - */ - protected void setName(final String name) { - this.name = name; - } - /** * Updates the specified message in the queue with a new visibility timeout value in seconds. * @@ -1394,7 +1413,7 @@ public void updateMessage(final CloudQueueMessage message, final int visibilityT } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.updateMessageImpl(message, visibilityTimeoutInSeconds, messageUpdateFields, options), @@ -1427,11 +1446,11 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { if (messageUpdateFields.contains(MessageUpdateFields.CONTENT)) { - StorageRequest.signBlobQueueAndFileRequest(connection, client, this.getLength(), null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, this.getLength(), context); } else { connection.setFixedLengthStreamingMode(0); - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } } @@ -1489,7 +1508,7 @@ public void uploadMetadata(QueueRequestOptions options, OperationContext opConte } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.uploadMetadataImpl(options), options.getRetryPolicyFactory(), opContext); @@ -1516,7 +1535,7 @@ public void setHeaders(HttpURLConnection connection, CloudQueue queue, Operation @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); } @Override @@ -1573,7 +1592,7 @@ public void uploadPermissions(final QueuePermissions permissions, QueueRequestOp } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.uploadPermissionsImpl(permissions, options), options.getRetryPolicyFactory(), opContext); @@ -1605,7 +1624,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, context); } @Override @@ -1677,7 +1696,7 @@ public QueuePermissions downloadPermissions(QueueRequestOptions options, Operati } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this.queueServiceClient); + options = QueueRequestOptions.populateAndApplyDefaults(options, this.queueServiceClient); return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, this.downloadPermissionsImpl(options), options.getRetryPolicyFactory(), opContext); @@ -1704,7 +1723,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -1762,7 +1781,7 @@ public String generateSharedAccessSignature(final SharedAccessQueuePolicy policy final String resourceName = this.getSharedAccessCanonicalName(); final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForQueue(policy, - groupPolicyIdentifier, resourceName, this.queueServiceClient, null); + groupPolicyIdentifier, resourceName, this.queueServiceClient); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForQueue(policy, groupPolicyIdentifier, signature); @@ -1779,7 +1798,7 @@ private String getSharedAccessCanonicalName() { String accountName = this.getServiceClient().getCredentials().getAccountName(); String queueName = this.getName(); - return String.format("/%s/%s", accountName, queueName); + return String.format("/%s/%s/%s", SR.QUEUE, accountName, queueName); } /** @@ -1803,56 +1822,42 @@ private final StorageUri getTransformedAddress(final OperationContext opContext) StorageException { return this.queueServiceClient.getCredentials().transformUri(this.getStorageUri(), opContext); } - + /** - * Parse Uri for SAS (Shared access signature) information. - * - * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding - * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise - * a new client is created based on SAS information or as anonymous credentials. + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. * * @param completeUri - * The complete Uri. - * @param existingClient - * The client to use. - * @param usePathStyleUris - * If true, path style Uris are used. - * @throws URISyntaxException + * A {@link StorageUri} object which represents the complete URI. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException + * If a storage service error occurred. */ - private void parseQueryAndVerify(final StorageUri completeUri, final CloudQueueClient existingClient, - final boolean usePathStyleUris) throws URISyntaxException, StorageException { + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper - .parseQuery(queryParameters); - - if (sasCreds == null) { - if (existingClient == null) { - throw new IllegalArgumentException(SR.STORAGE_CLIENT_OR_SAS_REQUIRED); - } - return; + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); } - final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, - existingClient.getCredentials()); - - if (existingClient == null || !sameCredentials) { + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); this.queueServiceClient = new CloudQueueClient(PathUtility.getServiceClientBaseAddress( - this.getStorageUri(), usePathStyleUris), sasCreds); + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getContainerNameFromUri(storageUri.getPrimaryUri(), usePathStyleUris); } - - if (existingClient != null && !sameCredentials) { - this.queueServiceClient.setDefaultRequestOptions(new QueueRequestOptions(existingClient - .getDefaultRequestOptions())); + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); } } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueueClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueueClient.java index 473ef29445ecd..893fe4b188ad0 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueueClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueueClient.java @@ -20,16 +20,15 @@ import java.net.URISyntaxException; import com.microsoft.azure.storage.DoesServiceRequest; -import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultContinuationType; import com.microsoft.azure.storage.ResultSegment; -import com.microsoft.azure.storage.RetryExponentialRetry; import com.microsoft.azure.storage.ServiceClient; import com.microsoft.azure.storage.ServiceProperties; import com.microsoft.azure.storage.ServiceStats; import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsAnonymous; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.blob.BlobRequestOptions; @@ -51,7 +50,7 @@ public final class CloudQueueClient extends ServiceClient { /** * Holds the default request option values associated with this Service Client. */ - private QueueRequestOptions defaultRequestOptions; + private QueueRequestOptions defaultRequestOptions = new QueueRequestOptions(); /** * Initializes a new instance of the CloudQueueClient class using the specified Queue service endpoint @@ -79,13 +78,10 @@ public CloudQueueClient(final URI baseUri, final StorageCredentials credentials) */ public CloudQueueClient(final StorageUri baseUri, final StorageCredentials credentials) { super(baseUri, credentials); - if (credentials == null) { - throw new IllegalArgumentException(SR.STORAGE_QUEUE_CREDENTIALS_NULL); + if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) { + throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS); } - - this.defaultRequestOptions = new QueueRequestOptions(); - this.defaultRequestOptions.setLocationMode(LocationMode.PRIMARY_ONLY); - this.defaultRequestOptions.setRetryPolicyFactory(new RetryExponentialRetry()); + QueueRequestOptions.applyDefaults(this.defaultRequestOptions); } /** @@ -103,6 +99,7 @@ public CloudQueueClient(final StorageUri baseUri, final StorageCredentials crede * If a storage service error occurred. * @see Naming Queues and Metadata */ + @SuppressWarnings("deprecation") public CloudQueue getQueueReference(final String queueName) throws URISyntaxException, StorageException { return new CloudQueue(queueName, this); } @@ -166,7 +163,7 @@ public Iterable listQueues(final String prefix, final QueueListingDe } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this); + options = QueueRequestOptions.populateAndApplyDefaults(options, this); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable(this.listQueuesSegmentedImpl(prefix, @@ -252,7 +249,7 @@ public ResultSegment listQueuesSegmented(final String prefix, } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this); + options = QueueRequestOptions.populateAndApplyDefaults(options, this); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); segmentedRequest.setToken(continuationToken); @@ -287,7 +284,7 @@ public HttpURLConnection buildRequest(CloudQueueClient client, Void parentObject @Override public void signRequest(HttpURLConnection connection, CloudQueueClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context); } @Override @@ -363,7 +360,7 @@ public ServiceStats getServiceStats(QueueRequestOptions options, OperationContex } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this); + options = QueueRequestOptions.populateAndApplyDefaults(options, this); return ExecutionEngine.executeWithRetry(this, null, this.getServiceStatsImpl(options, false), options.getRetryPolicyFactory(), opContext); @@ -406,7 +403,7 @@ public final ServiceProperties downloadServiceProperties(QueueRequestOptions opt } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this); + options = QueueRequestOptions.populateAndApplyDefaults(options, this); return ExecutionEngine.executeWithRetry(this, null, this.downloadServicePropertiesImpl(options, false), options.getRetryPolicyFactory(), opContext); @@ -455,7 +452,7 @@ public void uploadServiceProperties(final ServiceProperties properties, QueueReq } opContext.initialize(); - options = QueueRequestOptions.applyDefaults(options, this); + options = QueueRequestOptions.populateAndApplyDefaults(options, this); Utility.assertNotNull("properties", properties); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java index 654566457af79..347bcb20145a1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java @@ -42,8 +42,7 @@ public QueueRequestOptions(final QueueRequestOptions other) { } /** - * Populates the default timeout and retry policy from client if they are - * not set. + * Populates the default timeout and retry policy from client if they are not set. * * @param options * The input options to copy from when applying defaults @@ -51,16 +50,21 @@ public QueueRequestOptions(final QueueRequestOptions other) { * The {@link CloudQueueClient} service client to populate the * default values from. */ - protected static final QueueRequestOptions applyDefaults(QueueRequestOptions options, final CloudQueueClient client) { + protected static final QueueRequestOptions populateAndApplyDefaults(QueueRequestOptions options, final CloudQueueClient client) { QueueRequestOptions modifiedOptions = new QueueRequestOptions(options); RequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), true /* setStartTime */); - return QueueRequestOptions.applyDefaultsInternal(modifiedOptions, client); + QueueRequestOptions.applyDefaults(modifiedOptions); + return modifiedOptions; } - private static final QueueRequestOptions applyDefaultsInternal(QueueRequestOptions modifiedOptions, - CloudQueueClient client) { + /** + * Applies defaults to the options passed in. + * + * @param modifiedOptions + * The options to apply defaults to. + */ + protected static void applyDefaults(QueueRequestOptions modifiedOptions) { Utility.assertNotNull("modifiedOptions", modifiedOptions); RequestOptions.applyBaseDefaultsInternal(modifiedOptions); - return modifiedOptions; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/SharedAccessQueuePolicy.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/SharedAccessQueuePolicy.java index 908cfa1190f1c..09e0f5ed52d9f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/SharedAccessQueuePolicy.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/SharedAccessQueuePolicy.java @@ -54,7 +54,7 @@ public void setPermissions(final EnumSet permissio * Converts this policy's permissions to a string. * * @return A String that represents the shared access permissions in the "raup" format, which is - * described at {@link SharedAccessQueuePolicy#permissionsFromString(String)}. + * described at {@link SharedAccessQueuePolicy#setPermissionsFromString(String)}. */ @Override public String permissionsToString() { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java index 9e2b7d0756914..ca9b493fcae12 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java @@ -35,7 +35,6 @@ import com.microsoft.azure.storage.SharedAccessPolicyHandler; import com.microsoft.azure.storage.SharedAccessPolicySerializer; import com.microsoft.azure.storage.StorageCredentials; -import com.microsoft.azure.storage.StorageCredentialsAccountAndKey; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; @@ -58,7 +57,7 @@ public final class CloudTable { /** * The name of the table. */ - private final String name; + private String name; /** * Holds the list of URIs for all locations. @@ -115,10 +114,8 @@ public URI getUri() { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudTable(final URI uri) throws URISyntaxException, StorageException { + public CloudTable(final URI uri) throws StorageException { this(new StorageUri(uri, null)); } @@ -131,11 +128,39 @@ public CloudTable(final URI uri) throws URISyntaxException, StorageException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. */ - public CloudTable(final StorageUri uri) throws URISyntaxException, StorageException { - this(uri, null /* client*/); + public CloudTable(final StorageUri uri) throws StorageException { + this(uri, (StorageCredentials)null); + } + + /** + * Creates an instance of the CloudTable class using the specified table URI and credentials. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the table. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudTable(final URI uri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(uri, null), credentials); + } + + /** + * Creates an instance of the CloudTable class using the specified table StorageUri and credentials. + * + * @param uri + * A {@link StorageUri} object that represents the absolute StorageUri of the table. + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudTable(final StorageUri uri, final StorageCredentials credentials) throws StorageException { + this.parseQueryAndVerify(uri, credentials); } /** @@ -157,18 +182,17 @@ public CloudTable(final StorageUri uri) throws URISyntaxException, StorageExcept * If a storage service error occurred. * @see Understanding the Table Service Data * Model + * @deprecated as of 3.0.0. Please use {@link CloudTableClient#getTableReference(String)} */ + @Deprecated public CloudTable(final String tableName, final CloudTableClient client) throws URISyntaxException, StorageException { Utility.assertNotNull("client", client); Utility.assertNotNull("tableName", tableName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), tableName); - this.name = tableName; this.tableServiceClient = client; - - this.parseQueryAndVerify(this.storageUri, client, client.isUsePathStyleUris()); } /** @@ -184,16 +208,18 @@ public CloudTable(final String tableName, final CloudTableClient client) throws * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudTable#CloudTable(URI, StorageCredentials)} */ + @Deprecated public CloudTable(final URI uri, final CloudTableClient client) throws URISyntaxException, StorageException { this(new StorageUri(uri, null), client); } /** - * Creates an instance of the CloudTable class using the specified table URI and client. + * Creates an instance of the CloudTable class using the specified table StorageUri and client. * * @param uri - * A {@link StorageUri} object that represents the absolute URI of the table. + * A {@link StorageUri} object that represents the absolute StorageUri of the table. * @param client * A {@link CloudTableClient} object that represents the associated service client, and that specifies * the endpoint for the Table service. @@ -202,19 +228,16 @@ public CloudTable(final URI uri, final CloudTableClient client) throws URISyntax * If a storage service error occurred. * @throws URISyntaxException * If the resource URI is invalid. + * @deprecated as of 3.0.0. Please use {@link CloudTable#CloudTable(StorageUri, StorageCredentials)} */ + @Deprecated public CloudTable(final StorageUri uri, final CloudTableClient client) throws URISyntaxException, StorageException { - Utility.assertNotNull("storageUri", uri); - - this.storageUri = uri; - - boolean usePathStyleUris = client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()) - : client.isUsePathStyleUris(); - - this.name = PathUtility.getTableNameFromUri(uri.getPrimaryUri(), usePathStyleUris); - this.tableServiceClient = client; + this.parseQueryAndVerify(uri, client == null ? null : client.getCredentials()); - this.parseQueryAndVerify(this.storageUri, client, usePathStyleUris); + // Override the client set in parseQueryAndVerify to make sure request options are propagated. + if (client != null) { + this.tableServiceClient = client; + } } /** @@ -262,7 +285,7 @@ public void create(TableRequestOptions options, OperationContext opContext) thro } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); Utility.assertNotNullOrEmpty("tableName", this.name); @@ -306,7 +329,7 @@ public boolean createIfNotExists() throws StorageException { */ @DoesServiceRequest public boolean createIfNotExists(TableRequestOptions options, OperationContext opContext) throws StorageException { - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); boolean exists = this.exists(true, options, opContext); if (exists) { @@ -362,7 +385,7 @@ public void delete(TableRequestOptions options, OperationContext opContext) thro } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); Utility.assertNotNullOrEmpty("tableName", this.name); final DynamicTableEntity tableEntry = new DynamicTableEntity(); @@ -407,7 +430,7 @@ public boolean deleteIfExists() throws StorageException { */ @DoesServiceRequest public boolean deleteIfExists(TableRequestOptions options, OperationContext opContext) throws StorageException { - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); if (this.exists(true, options, opContext)) { try { @@ -492,7 +515,7 @@ public ArrayList execute(final TableBatchOperation batch, TableRequ } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.getServiceClient()); + options = TableRequestOptions.populateAndApplyDefaults(options, this.getServiceClient()); return batch.execute(this.getServiceClient(), this.getName(), options, opContext); } @@ -895,7 +918,7 @@ private boolean exists(final boolean primaryOnly, TableRequestOptions options, O } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); Utility.assertNotNullOrEmpty("tableName", this.name); @@ -957,7 +980,7 @@ public void uploadPermissions(final TablePermissions permissions, TableRequestOp } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); ExecutionEngine.executeWithRetry(this.tableServiceClient, this, this.uploadPermissionsImpl(permissions, options), options.getRetryPolicyFactory(), opContext); @@ -1064,7 +1087,7 @@ public TablePermissions downloadPermissions(TableRequestOptions options, Operati } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this.tableServiceClient); + options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient); return ExecutionEngine.executeWithRetry(this.tableServiceClient, this, this.downloadPermissionsImpl(options), options.getRetryPolicyFactory(), opContext); @@ -1161,18 +1184,11 @@ public String generateSharedAccessSignature(final SharedAccessTablePolicy policy final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForTable(policy, accessPolicyIdentifier, resourceName, startPartitionKey, startRowKey, endPartitionKey, endRowKey, - this.tableServiceClient, null); - - String accountKeyName = null; - StorageCredentials credentials = this.tableServiceClient.getCredentials(); - - if (credentials instanceof StorageCredentialsAccountAndKey) { - accountKeyName = ((StorageCredentialsAccountAndKey) credentials).getAccountKeyName(); - } + this.tableServiceClient); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForTable(policy, startPartitionKey, startRowKey, endPartitionKey, endRowKey, accessPolicyIdentifier, this.name, - signature, accountKeyName); + signature); return builder.toString(); } @@ -1186,59 +1202,44 @@ private String getSharedAccessCanonicalName() { String accountName = this.getServiceClient().getCredentials().getAccountName(); String tableNameLowerCase = this.getName().toLowerCase(Locale.ENGLISH); - return String.format("/%s/%s", accountName, tableNameLowerCase); + return String.format("/%s/%s/%s", SR.TABLE, accountName, tableNameLowerCase); } /** - * Parse Uri for SAS (Shared access signature) information. - * - * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding - * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise - * a new client is created based on SAS information or as anonymous credentials. - * + * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties. + * * @param completeUri * A {@link StorageUri} object which represents the complete URI. - * @param existingClient - * A {@link CloudTableClient} object which represents the client to use. - * @param usePathStyleUris - * true if path-style URIs are used; otherwise false. - * - * @throws URISyntaxException + * @param credentials + * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException - */ - private void parseQueryAndVerify(final StorageUri completeUri, final CloudTableClient existingClient, - final boolean usePathStyleUris) throws URISyntaxException, StorageException { + * If a storage service error occurred. + */ + private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) + throws StorageException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { - final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); - throw new IllegalArgumentException(errorMessage); + throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString())); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); + + final StorageCredentialsSharedAccessSignature parsedCredentials = + SharedAccessSignatureHelper.parseQuery(completeUri); - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper - .parseQuery(queryParameters); - - if (sasCreds == null) { - if (existingClient == null) { - throw new IllegalArgumentException(SR.STORAGE_CLIENT_OR_SAS_REQUIRED); - } - return; + if (credentials != null && parsedCredentials != null) { + throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED); } - final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, - existingClient.getCredentials()); - - if (existingClient == null || !sameCredentials) { - this.tableServiceClient = new CloudTableClient(new URI(PathUtility.getServiceClientBaseAddress( - this.getUri(), usePathStyleUris)), sasCreds); + try { + final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri()); + this.tableServiceClient = new CloudTableClient(PathUtility.getServiceClientBaseAddress( + this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials); + this.name = PathUtility.getTableNameFromUri(storageUri.getPrimaryUri(), usePathStyleUris); } - - if (existingClient != null && !sameCredentials) { - this.tableServiceClient.setDefaultRequestOptions(new TableRequestOptions(existingClient - .getDefaultRequestOptions())); + catch (final URISyntaxException e) { + throw Utility.generateNewUnexpectedStorageException(e); } } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTableClient.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTableClient.java index aacecba5de469..20d1ed9a4a458 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTableClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTableClient.java @@ -23,16 +23,15 @@ import java.util.HashMap; import com.microsoft.azure.storage.DoesServiceRequest; -import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultContinuationType; import com.microsoft.azure.storage.ResultSegment; -import com.microsoft.azure.storage.RetryExponentialRetry; import com.microsoft.azure.storage.ServiceClient; import com.microsoft.azure.storage.ServiceProperties; import com.microsoft.azure.storage.ServiceStats; import com.microsoft.azure.storage.StorageCredentials; +import com.microsoft.azure.storage.StorageCredentialsAnonymous; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.StorageUri; @@ -68,7 +67,7 @@ public final class CloudTableClient extends ServiceClient { /** * Holds the default request option values associated with this Service Client. */ - private TableRequestOptions defaultRequestOptions; + private TableRequestOptions defaultRequestOptions = new TableRequestOptions(); /** * Reserved for internal use. An {@link EntityResolver} that projects table entity data as a String @@ -108,14 +107,10 @@ public CloudTableClient(final URI baseUri, StorageCredentials credentials) { */ public CloudTableClient(final StorageUri baseUri, StorageCredentials credentials) { super(baseUri, credentials); - if (credentials == null) { - throw new IllegalArgumentException(SR.STORAGE_TABLE_CREDENTIALS_NULL); + if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) { + throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS); } - - this.defaultRequestOptions = new TableRequestOptions(); - this.defaultRequestOptions.setLocationMode(LocationMode.PRIMARY_ONLY); - this.defaultRequestOptions.setRetryPolicyFactory(new RetryExponentialRetry()); - this.defaultRequestOptions.setTablePayloadFormat(TablePayloadFormat.Json); + TableRequestOptions.applyDefaults(this.defaultRequestOptions); } /** @@ -137,6 +132,7 @@ public CloudTableClient(final StorageUri baseUri, StorageCredentials credentials * @see Understanding the Table Service Data * Model */ + @SuppressWarnings("deprecation") public CloudTable getTableReference(final String tableName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("tableName", tableName); return new CloudTable(tableName, this); @@ -361,7 +357,7 @@ protected ResultSegment executeQuerySegmentedImpl( } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this); + options = TableRequestOptions.populateAndApplyDefaults(options, this); Utility.assertContinuationType(continuationToken, ResultContinuationType.TABLE); @@ -407,8 +403,8 @@ public void signRequest(HttpURLConnection connection, CloudTableClient client, O public ResultSegment preProcessResponse(TableQuery queryRef, CloudTableClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { - throw TableServiceException.generateTableServiceException(true, this.getResult(), null, this - .getConnection().getErrorStream(), options.getTablePayloadFormat()); + throw TableServiceException.generateTableServiceException(this.getResult(), null, + this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } return null; @@ -482,8 +478,8 @@ public void signRequest(HttpURLConnection connection, CloudTableClient client, O public ResultSegment preProcessResponse(TableQuery queryRef, CloudTableClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { - throw TableServiceException.generateTableServiceException(true, this.getResult(), null, this - .getConnection().getErrorStream(), options.getTablePayloadFormat()); + throw TableServiceException.generateTableServiceException(this.getResult(), null, + this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } return null; @@ -557,7 +553,7 @@ protected Iterable generateIteratorForQuery(final } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this); + options = TableRequestOptions.populateAndApplyDefaults(options, this); SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); @@ -608,7 +604,7 @@ public ServiceStats getServiceStats(TableRequestOptions options, OperationContex } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this); + options = TableRequestOptions.populateAndApplyDefaults(options, this); return ExecutionEngine.executeWithRetry(this, null, this.getServiceStatsImpl(options, true), options.getRetryPolicyFactory(), opContext); @@ -652,7 +648,7 @@ public final ServiceProperties downloadServiceProperties(TableRequestOptions opt } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this); + options = TableRequestOptions.populateAndApplyDefaults(options, this); return ExecutionEngine.executeWithRetry(this, null, this.downloadServicePropertiesImpl(options, true), options.getRetryPolicyFactory(), opContext); @@ -701,7 +697,7 @@ public void uploadServiceProperties(final ServiceProperties properties, TableReq } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, this); + options = TableRequestOptions.populateAndApplyDefaults(options, this); Utility.assertNotNull("properties", properties); @@ -710,34 +706,6 @@ public void uploadServiceProperties(final ServiceProperties properties, TableReq options.getRetryPolicyFactory(), opContext); } - /** - * Gets the {@link TablePayloadFormat} that is used for any table accessed with this CloudTableClient - * object. Default is {@link TablePayloadFormat#Json} - * - * @return - * The {@link TablePayloadFormat} used by this CloudTableClient - * - * @deprecated use {@link #getDefaultRequestOptions().getTablePayloadFormat()} instead. - */ - @Deprecated - public TablePayloadFormat getTablePayloadFormat() { - return this.defaultRequestOptions.getTablePayloadFormat(); - } - - /** - * Sets the {@link TablePayloadFormat} that is used for any table accessed with this CloudTableClient - * object. - * - * @param payloadFormat - * The TablePayloadFormat to use. - * - * @deprecated use {@link #getDefaultRequestOptions().setTablePayloadFormat()} instead. - */ - @Deprecated - public void setTablePayloadFormat(TablePayloadFormat payloadFormat) { - this.defaultRequestOptions.setTablePayloadFormat(payloadFormat); - } - /** * Gets the {@link TableRequestOptions} that is used for requests associated with this CloudTableClient * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/DynamicTableEntity.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/DynamicTableEntity.java index b268105b9573a..6af9ddb758847 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/DynamicTableEntity.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/DynamicTableEntity.java @@ -82,7 +82,9 @@ public DynamicTableEntity(String partitionKey, String rowKey, final HashMapString which represents the row key of the {@link DynamicTableEntity} to be initialized. * @param etag - * The ETag of the {@link DynamicTableEntity} to be initialized. + * The ETag of the {@link DynamicTableEntity} to be initialized. This value is used to determine if the table + * entity has changed since it was last read from Microsoft Azure storage. The client cannot update this value + * on the service. * @param properties * A java.util.HashMap containing a map of String property names to * {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}. @@ -106,7 +108,10 @@ public DynamicTableEntity(String partitionKey, String rowKey, String etag, * @param properties * A java.util.HashMap containing a map of String property names to * {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}. + * @deprecated as of 3.0.0. The timestamp property is read-only, set by the service only. Please use + * {@link DynamicTableEntity#DynamicTableEntity(String, String, String, HashMap)} instead. */ + @Deprecated public DynamicTableEntity(String partitionKey, String rowKey, Date timestamp, String etag, final HashMap properties) { super(partitionKey, rowKey); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/Ignore.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/Ignore.java index 360e0a26bef36..fb0bbee606209 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/Ignore.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/Ignore.java @@ -27,7 +27,7 @@ * during reflection-based serialization and deserialization. See the documentation for {@link TableServiceEntity} for * more information on using reflection-based serialization and deserialization. * - * @see {@link StoreAs} + * @see StoreAs */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/SharedAccessTablePolicy.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/SharedAccessTablePolicy.java index 0b2964891bc82..5525ade6edbb7 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/SharedAccessTablePolicy.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/SharedAccessTablePolicy.java @@ -55,7 +55,7 @@ public void setPermissions(final EnumSet permissio * Converts this policy's permissions to a string. * * @return A String that represents the shared access permissions in the "raud" format, which is - * described at {@link SharedAccessTablePolicy#permissionsFromString(String)}. + * described at {@link SharedAccessTablePolicy#setPermissionsFromString(String)}. */ @Override public String permissionsToString() { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/StoreAs.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/StoreAs.java index 573559e2d1376..5493d19d9dca6 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/StoreAs.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/StoreAs.java @@ -39,7 +39,7 @@ * documentation for {@link TableServiceEntity} for more information on using reflection-based serialization and * deserialization. * - * @see {@link Ignore} + * @see Ignore */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java index 64030f9d4bd2d..fef13017746da 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java @@ -453,7 +453,7 @@ public HttpURLConnection buildRequest(CloudTableClient client, TableBatchOperati @Override public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context) throws Exception { - StorageRequest.signTableRequest(connection, client, -1L, opContext); + StorageRequest.signTableRequest(connection, client, -1L, context); } @Override @@ -493,29 +493,15 @@ public ArrayList postProcessResponse(HttpURLConnection connection, // Validate response if (currOp.getOperationType() == TableOperationType.INSERT) { - if (currOp.getEchoContent()) { - if (currMimePart.httpStatusCode == HttpURLConnection.HTTP_CONFLICT) { - throw new TableServiceException(currMimePart.httpStatusCode, - currMimePart.httpStatusMessage, currOp, new StringReader( - currMimePart.payload), options.getTablePayloadFormat()); - } - + if (currOp.getEchoContent() + && currMimePart.httpStatusCode != HttpURLConnection.HTTP_CREATED) { // Insert should receive created if echo content is on - if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_CREATED) { - failFlag = true; - } + failFlag = true; } - else { - if (currMimePart.httpStatusCode == HttpURLConnection.HTTP_CONFLICT) { - throw new TableServiceException(currMimePart.httpStatusCode, - currMimePart.httpStatusMessage, currOp, new StringReader( - currMimePart.payload), options.getTablePayloadFormat()); - } - + else if (!currOp.getEchoContent() + && currMimePart.httpStatusCode != HttpURLConnection.HTTP_NO_CONTENT) { // Insert should receive no content if echo content is off - if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_NO_CONTENT) { - failFlag = true; - } + failFlag = true; } } else if (currOp.getOperationType() == TableOperationType.RETRIEVE) { @@ -531,14 +517,6 @@ else if (currOp.getOperationType() == TableOperationType.RETRIEVE) { } } else { - // Validate response code. - if (currMimePart.httpStatusCode == HttpURLConnection.HTTP_NOT_FOUND) { - // Throw so as to not retry. - throw new TableServiceException(currMimePart.httpStatusCode, - currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload), - options.getTablePayloadFormat()); - } - if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_NO_CONTENT) { // All others should receive no content. (delete, merge, upsert etc) failFlag = true; @@ -546,11 +524,9 @@ currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload), } if (failFlag) { - TableServiceException potentiallyRetryableException = new TableServiceException( - currMimePart.httpStatusCode, currMimePart.httpStatusMessage, currOp, - new StringReader(currMimePart.payload), options.getTablePayloadFormat()); - potentiallyRetryableException.setRetryable(true); - throw potentiallyRetryableException; + throw new TableServiceException(currMimePart.httpStatusCode, + currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload), + options.getTablePayloadFormat()); } ByteArrayInputStream byteStream = null; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableDeserializer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableDeserializer.java index 0df45b155fbb0..5b56339e14c40 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableDeserializer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableDeserializer.java @@ -186,6 +186,7 @@ static TableResult parseSingleOpResponse(final InputS * @throws JsonParseException * if an error occurs while parsing the stream. */ + @SuppressWarnings("deprecation") private static TableResult parseJsonEntity(final JsonParser parser, final Class clazzType, HashMap classProperties, final EntityResolver resolver, final TableRequestOptions options, final OperationContext opContext) throws JsonParseException, @@ -545,6 +546,7 @@ private static TableResult parseSingleOpJsonResponse( * @throws StorageException * if a storage service error occurs. */ + @SuppressWarnings("deprecation") private static TableResult parseAtomEntity(final XMLStreamReader xmlr, final Class clazzType, final EntityResolver resolver, final TableRequestOptions options, final OperationContext opContext) throws XMLStreamException, InstantiationException, diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntity.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntity.java index 56b7db166076d..2c695c74d8a78 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntity.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntity.java @@ -74,8 +74,8 @@ public interface TableEntity { /** - * Gets the ETag value for the entity. This value is used to determine if the table entity has changed since it was - * last read from Microsoft Azure storage. + * Gets the ETag value to verify for the entity. This value is used to determine if the table entity has changed + * since it was last read from Microsoft Azure storage. The client cannot update this value on the service. * * @return * A String which represents the ETag for the entity. @@ -99,7 +99,7 @@ public interface TableEntity { public String getRowKey(); /** - * Gets the Timestamp for the entity. + * Gets the Timestamp for the entity. The server manages the value of Timestamp, which cannot be modified. * * @return * A java.util.Date object which represents the Timestamp value for the entity. @@ -123,7 +123,8 @@ public void readEntity(HashMap properties, OperationCont throws StorageException; /** - * Sets the ETag for the entity. + * Sets the ETag value to verify for the entity. This value is used to determine if the table entity has changed + * since it was last read from Microsoft Azure storage. The client cannot update this value on the service. * * @param etag * A String which specifies the ETag to set for the entity. @@ -151,7 +152,9 @@ public void readEntity(HashMap properties, OperationCont * * @param timeStamp * A java.util.Date which specifies the Timestamp value to set for the entity. + * @deprecated as of 3.0.0. The timestamp property is a read-only property, set by the service only. */ + @Deprecated public void setTimestamp(Date timeStamp); /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntitySerializer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntitySerializer.java index 8c39d59909847..7de22252ebd58 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntitySerializer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableEntitySerializer.java @@ -126,6 +126,7 @@ static void writeSingleEntityToString(final StringWriter strWriter, final TableP * @throws StorageException * if a Storage service error occurs. */ + @SuppressWarnings("deprecation") private static void writeAtomEntity(final TableEntity entity, final boolean isTableEntry, final XMLStreamWriter xmlw, final OperationContext opContext) throws XMLStreamException, StorageException { HashMap properties = entity.writeEntity(opContext); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java index b785393fd0cc2..4257e2e087009 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java @@ -47,8 +47,7 @@ public class TableOperation { /** * A static factory method returning a {@link TableOperation} instance to delete the specified entity from Microsoft * Azure storage. To execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with the * * @param entity * The object instance implementing {@link TableEntity} to associate with the operation. @@ -64,8 +63,8 @@ public static TableOperation delete(final TableEntity entity) { /** * A static factory method returning a {@link TableOperation} instance to insert the specified entity into * Microsoft Azure storage. To execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with the + * * @param entity * The object instance implementing {@link TableEntity} to associate with the operation. @@ -79,8 +78,7 @@ public static TableOperation insert(final TableEntity entity) { /** * A static factory method returning a {@link TableOperation} instance to insert the specified entity into * Microsoft Azure storage. To execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with the * * @param entity * The object instance implementing {@link TableEntity} to associate with the operation. @@ -98,7 +96,7 @@ public static TableOperation insert(final TableEntity entity, boolean echoConten * A static factory method returning a {@link TableOperation} instance to merge the specified entity into * Microsoft Azure storage, or insert it if it does not exist. To execute this {@link TableOperation} on a given * table, call - * the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with + * the {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with * the table name and the {@link TableOperation} as arguments. * * @param entity @@ -115,7 +113,7 @@ public static TableOperation insertOrMerge(final TableEntity entity) { * A static factory method returning a {@link TableOperation} instance to replace the specified entity in * Microsoft Azure storage, or insert it if it does not exist. To execute this {@link TableOperation} on a given * table, call - * the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with + * the {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with * the table name and the {@link TableOperation} as arguments. * * @param entity @@ -131,8 +129,7 @@ public static TableOperation insertOrReplace(final TableEntity entity) { /** * A static factory method returning a {@link TableOperation} instance to merge the specified table entity into * Microsoft Azure storage. To execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with the * * @param entity * The object instance implementing {@link TableEntity} to associate with the operation. @@ -148,8 +145,7 @@ public static TableOperation merge(final TableEntity entity) { /** * A static factory method returning a {@link TableOperation} instance to retrieve the specified table entity and * return it as the specified type. To execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance with the * * @param partitionKey * A String which specifies the PartitionKey value for the entity to retrieve. @@ -170,7 +166,7 @@ public static TableOperation retrieve(final String partitionKey, final String ro /** * A static factory method returning a {@link TableOperation} instance to retrieve the specified table entity and * return a projection of it using the specified resolver. To execute this {@link TableOperation} on a given table, - * call the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance + * call the {@link CloudTable#execute(TableOperation)} method on a {@link CloudTableClient} instance * with the table name and the {@link TableOperation} as arguments. * * @param partitionKey @@ -192,8 +188,7 @@ public static TableOperation retrieve(final String partitionKey, final String ro /** * A static factory method returning a {@link TableOperation} instance to replace the specified table entity. To * execute this {@link TableOperation} on a given table, call the - * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the - * table name and the {@link TableOperation} as arguments. + * {@link CloudTable#execute(TableOperation)} method. * * @param entity * The object instance implementing {@link TableEntity} to associate with the operation. @@ -322,15 +317,9 @@ public void signRequest(HttpURLConnection connection, CloudTableClient client, O @Override public TableResult preProcessResponse(TableOperation operation, CloudTableClient client, OperationContext context) throws Exception { - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND - || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { - throw TableServiceException.generateTableServiceException(false, this.getResult(), operation, this - .getConnection().getErrorStream(), options.getTablePayloadFormat()); - } - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { - throw TableServiceException.generateTableServiceException(true, this.getResult(), operation, this - .getConnection().getErrorStream(), options.getTablePayloadFormat()); + throw TableServiceException.generateTableServiceException(this.getResult(), operation, + this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } return operation.parseResponse(null, this.getResult().getStatusCode(), null, opContext, options); @@ -415,58 +404,33 @@ opContext, tableName, generateRequestIdentity(isTableEntry, tableIdentity), @Override public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context) throws Exception { - StorageRequest.signTableRequest(connection, client, -1L, opContext); + StorageRequest.signTableRequest(connection, client, -1L, context); } @Override public TableResult preProcessResponse(TableOperation operation, CloudTableClient client, OperationContext context) throws Exception { if (operation.opType == TableOperationType.INSERT) { - if (operation.getEchoContent()) { + if (operation.getEchoContent() + && this.getResult().getStatusCode() == HttpURLConnection.HTTP_CREATED) { // Insert should receive created if echo content is on - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CREATED) { - return new TableResult(); - } - else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { - throw TableServiceException.generateTableServiceException(false, this.getResult(), - operation, this.getConnection().getErrorStream(), - options.getTablePayloadFormat()); - } - else { - throw TableServiceException.generateTableServiceException(true, this.getResult(), - operation, this.getConnection().getErrorStream(), - options.getTablePayloadFormat()); - } + return new TableResult(); } - else { + else if (!operation.getEchoContent() + && this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { // Insert should receive no content if echo content is off - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return operation.parseResponse(null, this.getResult().getStatusCode(), this - .getConnection().getHeaderField(TableConstants.HeaderConstants.ETAG), - opContext, options); - } - else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { - throw TableServiceException.generateTableServiceException(false, this.getResult(), - operation, this.getConnection().getErrorStream(), - options.getTablePayloadFormat()); - } - else { - throw TableServiceException.generateTableServiceException(true, this.getResult(), - operation, this.getConnection().getErrorStream(), - options.getTablePayloadFormat()); - } - } - } - else { - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); } - else { - throw TableServiceException.generateTableServiceException(true, this.getResult(), - operation, this.getConnection().getErrorStream(), options.getTablePayloadFormat()); - } } + else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { + // InsertOrMerge and InsertOrReplace should always receive no content + return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() + .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); + } + + throw TableServiceException.generateTableServiceException(this.getResult(), operation, + this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } @Override @@ -565,26 +529,19 @@ public HttpURLConnection buildRequest(CloudTableClient client, TableOperation op @Override public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context) throws Exception { - StorageRequest.signTableRequest(connection, client, -1L, opContext); + StorageRequest.signTableRequest(connection, client, -1L, context); } @Override public TableResult preProcessResponse(TableOperation operation, CloudTableClient client, OperationContext context) throws Exception { - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND - || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { - throw TableServiceException.generateTableServiceException(false, this.getResult(), operation, + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + throw TableServiceException.generateTableServiceException(this.getResult(), operation, this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() - .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); - } - else { - throw TableServiceException.generateTableServiceException(true, this.getResult(), operation, - this.getConnection().getErrorStream(), options.getTablePayloadFormat()); - } + return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() + .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); } @Override @@ -670,26 +627,19 @@ context, tableName, generateRequestIdentity(false, null), operation.getEntity() @Override public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context) throws Exception { - StorageRequest.signTableRequest(connection, client, -1L, opContext); + StorageRequest.signTableRequest(connection, client, -1L, context); } @Override public TableResult preProcessResponse(TableOperation operation, CloudTableClient client, OperationContext context) throws Exception { - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND - || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { - throw TableServiceException.generateTableServiceException(false, this.getResult(), operation, + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + throw TableServiceException.generateTableServiceException(this.getResult(), operation, this.getConnection().getErrorStream(), options.getTablePayloadFormat()); } - if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() - .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); - } - else { - throw TableServiceException.generateTableServiceException(true, this.getResult(), operation, - this.getConnection().getErrorStream(), options.getTablePayloadFormat()); - } + return operation.parseResponse(null, this.getResult().getStatusCode(), this.getConnection() + .getHeaderField(TableConstants.HeaderConstants.ETAG), opContext, options); } @Override @@ -743,7 +693,7 @@ protected TableResult execute(final CloudTableClient client, final String tableN } opContext.initialize(); - options = TableRequestOptions.applyDefaults(options, client); + options = TableRequestOptions.populateAndApplyDefaults(options, client); Utility.assertNotNullOrEmpty(TableConstants.TABLE_NAME, tableName); if (this.getOperationType() == TableOperationType.INSERT diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TablePayloadFormat.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TablePayloadFormat.java index 6b246c8105b06..7aea503017935 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TablePayloadFormat.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TablePayloadFormat.java @@ -23,7 +23,7 @@ public enum TablePayloadFormat { /** * Use AtomPub. * - * @Deprecated Deprecated as of 0.7.0 in favor of Json format. + * @deprecated Deprecated as of 0.7.0 in favor of Json format. */ @Deprecated AtomPub, diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableQuery.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableQuery.java index 2cf93c4357d05..98a6550163104 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableQuery.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableQuery.java @@ -33,7 +33,7 @@ *

    * To create a table query with fluent syntax, the {@link #from} static factory method and the {@link #where}, * {@link #select}, and {@link #take} mutator methods each return a reference to the object which can be chained into a - * single expression. Use the {@link TableQuery#from(String, Class)} static class factory method to create a + * single expression. Use the {@link TableQuery#from(Class)} static class factory method to create a * TableQuery instance that executes on the named table with entities of the specified {@link TableEntity} * implementing type. Use the {@link #where} method to specify a filter expression for the entities returned. Use the * {@link #select} method to specify the table entity properties to return. Use the {@link #take} method to limit the @@ -496,20 +496,6 @@ public String[] getColumns() { return this.columns; } - /** - * Gets the class type of the table entities returned by the query. - * - * @return - * The java.lang.Class of the class T implementing the {@link TableEntity} - * interface that - * represents the table entity type for the query. - * @deprecated Deprecated as of 0.7.0. Please use getClazzType() instead. - */ - @Deprecated - public Class getEntityClass() { - return this.clazzType; - } - /** * Gets the filter expression specified in the table query. All entities in the table are returned by * default if no filter expression is specified in the table query. A filter for the entities to return may be diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableRequestOptions.java index 121e0417e4539..fdf1a15b33b27 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableRequestOptions.java @@ -66,7 +66,7 @@ public interface PropertyResolver { * that may have been written using versions of this library prior to 2.0.0. * See here for more details. */ - private boolean dateBackwardCompatibility = false; + private Boolean dateBackwardCompatibility; /** * Creates an instance of the TableRequestOptions @@ -87,38 +87,47 @@ public TableRequestOptions(final TableRequestOptions other) { if (other != null) { this.setTablePayloadFormat(other.getTablePayloadFormat()); this.setPropertyResolver(other.getPropertyResolver()); - this.dateBackwardCompatibility = other.dateBackwardCompatibility; + this.setDateBackwardCompatibility(other.getDateBackwardCompatibility()); } } /** - * Reserved for internal use. Initializes the values for this TableRequestOptions instance, if they are - * currently null, using the values specified in the {@link CloudTableClient} parameter. + * Initializes the values for this TableRequestOptions instance, if they are currently + * null, using the values specified in the {@link CloudTableClient} parameter. * * @param options - * A {@link TableRequestOptions} object which represents the input options to copy from when applying defaults. + * A {@link TableRequestOptions} object which represents the input options to copy from when applying + * defaults. * @param client * A {@link CloudTableClient} object from which to copy the timeout and retry policy. * * @return A {@link TableRequestOptions} object. * */ - protected static final TableRequestOptions applyDefaults(final TableRequestOptions options, + protected static final TableRequestOptions populateAndApplyDefaults(final TableRequestOptions options, final CloudTableClient client) { TableRequestOptions modifiedOptions = new TableRequestOptions(options); - TableRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions()); - return TableRequestOptions.applyDefaultsInternal(modifiedOptions, client); + TableRequestOptions.populate(modifiedOptions, client.getDefaultRequestOptions()); + TableRequestOptions.applyDefaults(modifiedOptions); + return modifiedOptions; } - private static final TableRequestOptions applyDefaultsInternal(final TableRequestOptions modifiedOptions, - CloudTableClient client) { + /** + * Applies defaults to the options passed in. + * + * @param modifiedOptions + * The options to apply defaults to. + */ + protected static void applyDefaults(final TableRequestOptions modifiedOptions) { Utility.assertNotNull("modifiedOptions", modifiedOptions); RequestOptions.applyBaseDefaultsInternal(modifiedOptions); if (modifiedOptions.getTablePayloadFormat() == null) { modifiedOptions.setTablePayloadFormat(TablePayloadFormat.Json); } - - return modifiedOptions; + + if (modifiedOptions.getDateBackwardCompatibility() == null) { + modifiedOptions.setDateBackwardCompatibility(false); + } } /** @@ -131,8 +140,7 @@ private static final TableRequestOptions applyDefaultsInternal(final TableReques * * @return A {@link RequestOptions} object. */ - private static final RequestOptions populateRequestOptions(TableRequestOptions modifiedOptions, - final TableRequestOptions clientOptions) { + private static void populate(TableRequestOptions modifiedOptions, final TableRequestOptions clientOptions) { RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, true /* setStartTime */); if (modifiedOptions.getTablePayloadFormat() == null) { modifiedOptions.setTablePayloadFormat(clientOptions.getTablePayloadFormat()); @@ -141,8 +149,10 @@ private static final RequestOptions populateRequestOptions(TableRequestOptions m if (modifiedOptions.getPropertyResolver() == null) { modifiedOptions.setPropertyResolver(clientOptions.getPropertyResolver()); } - - return modifiedOptions; + + if (modifiedOptions.getDateBackwardCompatibility() == null) { + modifiedOptions.setDateBackwardCompatibility(clientOptions.getDateBackwardCompatibility()); + } } /** @@ -171,14 +181,14 @@ public PropertyResolver getPropertyResolver() { /** * Gets whether the client should look to correct Date values stored on a {@link TableEntity} * that may have been written using versions of this library prior to 2.0.0, - * see {@link #setDateBackwardCompatibility(boolean)}. + * see {@link #setDateBackwardCompatibility(Boolean)}. *

    * See here for more details. * * @return * true if dateBackwardCompatibility is enabled; otherwise, false */ - public boolean getDateBackwardCompatibility() { + public Boolean getDateBackwardCompatibility() { return this.dateBackwardCompatibility; } @@ -187,7 +197,7 @@ public boolean getDateBackwardCompatibility() { *

    * The default {@link TablePayloadFormat} is set in the client and is by default {@link TablePayloadFormat#Json}. * You can change the {@link TablePayloadFormat} on this request by setting this property. You can also change the - * value on the {@link TableServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made + * value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all subsequent requests made * via the service client will use that {@link TablePayloadFormat}. * * @param payloadFormat @@ -203,7 +213,7 @@ public void setTablePayloadFormat(TablePayloadFormat payloadFormat) { *

    * The default {@link PropertyResolver} is set in the client and is by default null, indicating not to use a * property resolver. You can change the {@link PropertyResolver} on this request by setting this property. You can - * also change the value on the {@link TableServiceClient#getDefaultRequestOptions()} object so that all subsequent + * also change the value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all subsequent * requests made via the service client will use that {@link PropertyResolver}. * * @param propertyResolver @@ -219,7 +229,7 @@ public void setPropertyResolver(PropertyResolver propertyResolver) { *

    * {@link #dateBackwardCompatibility} is by default false, indicating a post 2.0.0 version or mixed- * platform usage. You can change the {@link #dateBackwardCompatibility} on this request by setting this property. - * You can also change the value on the {@link TableServiceClient#getDefaultRequestOptions()} object so that all + * You can also change the value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all * subsequent requests made via the service client will use that {@link #dateBackwardCompatibility}. *

    * See here for more details. @@ -227,7 +237,7 @@ public void setPropertyResolver(PropertyResolver propertyResolver) { * @param dateBackwardCompatibility * true to enable dateBackwardCompatibility; otherwise, false */ - public void setDateBackwardCompatibility(boolean dateBackwardCompatibility) { + public void setDateBackwardCompatibility(Boolean dateBackwardCompatibility) { this.dateBackwardCompatibility = dateBackwardCompatibility; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableResult.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableResult.java index 8be6cf699fec4..86d9cc76dc13a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableResult.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableResult.java @@ -157,6 +157,7 @@ protected void setResult(final Object result) { * An instance of an object implementing {@link TableEntity} to associate with the table operation. * @throws UnsupportedEncodingException */ + @SuppressWarnings("deprecation") protected void updateResultObject(final TableEntity ent) throws UnsupportedEncodingException { this.result = ent; ent.setEtag(this.etag); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceEntity.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceEntity.java index 7f4cc9249de38..be4c89e5b42fb 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceEntity.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceEntity.java @@ -279,8 +279,8 @@ public TableServiceEntity(String partitionKey, String rowKey) { } /** - * Gets the ETag value for the entity. This value is used to determine if the table entity has changed since it was - * last read from Microsoft Azure storage. + * Gets the ETag value to verify for the entity. This value is used to determine if the table entity has changed + * since it was last read from Microsoft Azure storage. The client cannot update this value on the service. * * @return * A String containing the ETag for the entity. @@ -313,10 +313,10 @@ public String getRowKey() { } /** - * Gets the timeStamp value for the entity. + * Gets the Timestamp for the entity. The server manages the value of Timestamp, which cannot be modified. * * @return - * A java.util.Date containing the timeStamp value for the entity. + * A java.util.Date object which represents the Timestamp value for the entity. */ @Override public Date getTimestamp() { @@ -394,8 +394,8 @@ public void readEntity(final HashMap properties, final O } /** - * Sets the ETag value for the entity. This value is used to determine if the table entity has changed since it was - * last read from Microsoft Azure storage. + * Sets the ETag value to verify for the entity. This value is used to determine if the table entity has changed + * since it was last read from Microsoft Azure storage. The client cannot update this value on the service. * * @param etag * A String containing the ETag for the entity. @@ -432,7 +432,9 @@ public void setRowKey(final String rowKey) { * * @param timeStamp * A java.util.Date containing the timeStamp value for the entity. + * @deprecated as of 3.0.0. The timestamp property is a read-only property, set by the service only. */ + @Deprecated @Override public void setTimestamp(final Date timeStamp) { this.timeStamp = timeStamp; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceException.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceException.java index 726e5fd6e9427..93d4de9e3ce3e 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceException.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableServiceException.java @@ -15,14 +15,10 @@ package com.microsoft.azure.storage.table; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import javax.xml.stream.XMLStreamException; - -import com.fasterxml.jackson.core.JsonParseException; import com.microsoft.azure.storage.RequestResult; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageExtendedErrorInformation; @@ -36,17 +32,9 @@ public final class TableServiceException extends StorageException { private TableOperation operation; - /** - * Reserved for internal use. This flag indicates whether the operation that threw the exception can be retried. - */ - private boolean retryable = false; - /** * Reserved for internal use. A static factory method to create a {@link TableServiceException} instance using * the specified parameters. - * - * @param retryable - * true if the table operation can be retried; otherwise, false. * @param res * A {@link RequestResult} containing the result of the table storage service operation. * @param op @@ -55,16 +43,14 @@ public final class TableServiceException extends StorageException { * The java.io.InputStream of the error response from the table operation request. * @param format * The {@link TablePayloadFormat} to use for parsing + * * @return * A {@link TableServiceException} instance initialized with values from the input parameters. */ - protected static TableServiceException generateTableServiceException(boolean retryable, RequestResult res, - TableOperation op, InputStream inStream, TablePayloadFormat format) { - TableServiceException retryableException = new TableServiceException(res.getStatusCode(), - res.getStatusMessage(), op, new InputStreamReader(inStream), format); - retryableException.retryable = retryable; - - return retryableException; + protected static TableServiceException generateTableServiceException(RequestResult res, TableOperation op, + InputStream inStream, TablePayloadFormat format) { + return new TableServiceException(res.getStatusCode(), res.getStatusMessage(), op, + new InputStreamReader(inStream), format); } /** @@ -115,14 +101,8 @@ protected TableServiceException(final int httpStatusCode, final String message, this.extendedErrorInformation = TableStorageErrorDeserializer.getExtendedErrorInformation(reader, format); this.errorCode = this.extendedErrorInformation.getErrorCode(); } - catch (XMLStreamException e) { - // no-op, if error parsing fails, just throw first exception. - } - catch (JsonParseException e) { - // no-op, if error parsing fails, just throw first exception. - } - catch (IOException e) { - // no-op, if error parsing fails, just throw first exception. + catch (Exception e) { + // no-op, if error parsing fails, just throw original exception. } } } @@ -138,17 +118,6 @@ public TableOperation getOperation() { return this.operation; } - /** - * Reserved for internal use. Gets a flag indicating the table operation can be retried. - * - * @return - * The boolean flag indicating whether the table operation that caused the exception can be - * retried. - */ - public boolean isRetryable() { - return this.retryable; - } - /** * Reserved for internal use. Sets the table operation that caused the TableServiceException to be * thrown. @@ -160,15 +129,4 @@ public boolean isRetryable() { protected void setOperation(final TableOperation operation) { this.operation = operation; } - - /** - * Reserved for internal use. Sets a flag indicating the table operation can be retried. - * - * @param retryable - * The boolean flag to set indicating whether the table operation that caused the exception - * can be retried. - */ - protected void setRetryable(boolean retryable) { - this.retryable = retryable; - } } diff --git a/pom.xml b/pom.xml index 972dd615142bc..030d4e6c3df75 100644 --- a/pom.xml +++ b/pom.xml @@ -1,23 +1,16 @@ - + 4.0.0 com.microsoft.azure azure-storage - 2.2.0 + 3.0.0 jar Microsoft Azure Storage Client SDK @@ -53,34 +46,34 @@ junit junit - 4.10 + 4.12 test com.fasterxml.jackson.core jackson-core - 2.2.3 + 2.6.0 org.slf4j slf4j-api - 1.7.5 + 1.7.12 org.apache.commons commons-lang3 - 3.3.2 + 3.4 microsoft-azure-storage/src microsoft-azure-storage-test/src - - - microsoft-azure-storage-test/res - - + + + microsoft-azure-storage-test/res + + org.apache.maven.plugins @@ -94,22 +87,6 @@ - - org.apache.maven.plugins - maven-help-plugin - 2.1.1 - - - validate - - evaluate - - - legal - - - - org.apache.maven.plugins @@ -120,83 +97,19 @@ 1.6 - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8 - - *.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.storage - /** -
    * Copyright 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. -
    */]]>
    -
    -
    - - - org.codehaus.mojo - findbugs-maven-plugin - 2.3.2 - - true - true - true - - org.apache.maven.plugins - maven-checkstyle-plugin + maven-javadoc-plugin 2.8 - src/config/checkstyle.xml + *.core + public -
    - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.apache.maven.plugins - maven-help-plugin - [2.1.1,) - - evaluate - - - - - - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.4.3 - org.apache.maven.plugins maven-surefire-plugin @@ -216,20 +129,4 @@
    - - - - org.codehaus.mojo - emma-maven-plugin - 1.0-alpha-3 - true - - - org.codehaus.mojo - surefire-report-maven-plugin - 2.0-beta-1 - true - - -