diff --git a/ChangeLog.txt b/ChangeLog.txt index 1be7592..126f399 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,11 @@ +2014.12.22 Version 0.4.0 + * Deprecated getSubDirectoryReference() for blob directories and file directories. Use getDirectoryReference() instead. + * Fixed a bug where maxResults was not verified to be positive for list operations. + * Deprecated AuthenticationScheme and its getter and setter. In the future only SharedKey will be used. + * Added support for EndpointSuffix which was previously not accepted in Account Strings. + * Fixed a bug where high precision Date values stored on Table Entites were forced to fit into milliseconds resulting in inaccuracies. Precision is limited to 1 millisecond by the Android Date class. If greater precision is required, the String should be used directly. + * Added TableRequestOptions.dateBackwardCompatibility, which supports reading Date values on Table Entities written using versions of this library prior to 0.4.0. See http://go.microsoft.com/fwlink/?LinkId=523753 for more details. + 2014.10.10 Version 0.3.1 * Fixed a bug where a NullPointerException was thrown instead of a NetworkOnMainThreadException if code was executed on the main thread. diff --git a/microsoft-azure-storage-samples/AndroidManifest.xml b/microsoft-azure-storage-samples/AndroidManifest.xml index 105a365..37c8d4f 100644 --- a/microsoft-azure-storage-samples/AndroidManifest.xml +++ b/microsoft-azure-storage-samples/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="0.4.0" > + android:versionName="0.4.0" > = 0); // ticks is non-negative + assertTrue(ticks <= 9999); // ticks do not overflow into milliseconds + long expectedMilliseconds = intendedMilliseconds; + + if (dateBackwardCompatibility && (intendedMilliseconds % 1000 == 0) && (ticks < 1000)) { + // when no milliseconds are present dateBackwardCompatibility causes up to 3 digits of ticks + // to be read as milliseconds + expectedMilliseconds += ticks; + } else if (writtenPre2 && !dateBackwardCompatibility && (ticks == 0)) { + // without DateBackwardCompatibility, milliseconds stored by Java prior to 0.4.0 are lost + expectedMilliseconds -= expectedMilliseconds % 1000; + } + + assertEquals(expectedMilliseconds, Utility.parseDate(dateString, dateBackwardCompatibility).getTime()); + } + + public void testDateStringFormatting() { + String fullDateString = "2014-12-07T09:15:12.123Z"; + String outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); + assertEquals(fullDateString, outDateString); + + fullDateString = "2015-01-14T14:53:32.800Z"; + outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); + assertEquals(fullDateString, outDateString); + + // Ensure that trimming of trailing zeroes by the service does not affect this + fullDateString = "2015-01-14T14:53:32.8Z"; + outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); + fullDateString = fullDateString.replace("Z", "00Z"); + assertEquals(fullDateString, outDateString); + + // Ensure that trimming of trailing zeroes by the service does not affect this + // even with dateBackwardCompatibility + fullDateString = "2015-01-14T14:53:32.0000800Z"; + outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString, true)); + fullDateString = "2015-01-14T14:53:32.800Z"; + assertEquals(fullDateString, outDateString); + } + private static String generateRandomContainerName() { String containerName = "container" + UUID.randomUUID().toString(); return containerName.replace("-", ""); 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 bf4471e..871eac8 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 @@ -35,8 +35,8 @@ public class StorageAccountTests extends TestCase { - public static String accountName = UUID.randomUUID().toString(); - public static String accountKey = Base64.encode(UUID.randomUUID().toString().getBytes()); + public static final String ACCOUNT_NAME = UUID.randomUUID().toString(); + public static final String ACCOUNT_KEY = Base64.encode(UUID.randomUUID().toString().getBytes()); public void testStorageCredentialsAnonymous() throws URISyntaxException, StorageException { StorageCredentialsAnonymous cred = new StorageCredentialsAnonymous(); @@ -48,22 +48,22 @@ public void testStorageCredentialsAnonymous() throws URISyntaxException, Storage } public void testStorageCredentialsSharedKey() throws URISyntaxException, StorageException { - StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(accountName, accountKey); + StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); - assertEquals(accountName, cred.getAccountName()); + assertEquals(ACCOUNT_NAME, cred.getAccountName()); URI testUri = new URI("http://test/abc?querya=1"); assertEquals(testUri, cred.transformUri(testUri)); - assertEquals(accountKey, cred.getCredentials().exportBase64EncodedKey()); + assertEquals(ACCOUNT_KEY, cred.getCredentials().exportBase64EncodedKey()); byte[] dummyKey = { 0, 1, 2 }; String base64EncodedDummyKey = Base64.encode(dummyKey); - cred = new StorageCredentialsAccountAndKey(accountName, base64EncodedDummyKey); + cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, base64EncodedDummyKey); assertEquals(base64EncodedDummyKey, cred.getCredentials().exportBase64EncodedKey()); dummyKey[0] = 3; base64EncodedDummyKey = Base64.encode(dummyKey); - cred = new StorageCredentialsAccountAndKey(accountName, base64EncodedDummyKey); + cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, base64EncodedDummyKey); assertEquals(base64EncodedDummyKey, cred.getCredentials().exportBase64EncodedKey()); } @@ -85,17 +85,17 @@ public void testStorageCredentialsSAS() throws URISyntaxException, StorageExcept public void testStorageCredentialsEmptyKeyValue() throws URISyntaxException, InvalidKeyException { String emptyKeyValueAsString = ""; String emptyKeyConnectionString = String.format(Locale.US, - "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=", accountName); + "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=", ACCOUNT_NAME); - StorageCredentialsAccountAndKey credentials1 = new StorageCredentialsAccountAndKey(accountName, + StorageCredentialsAccountAndKey credentials1 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, emptyKeyValueAsString); - assertEquals(accountName, credentials1.getAccountName()); + 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(accountName, account1.getCredentials().getAccountName()); + assertEquals(ACCOUNT_NAME, account1.getCredentials().getAccountName()); assertEquals(emptyKeyValueAsString, Base64.encode(((StorageCredentialsAccountAndKey) (account1.getCredentials())).getCredentials() .exportKey())); @@ -103,19 +103,19 @@ public void testStorageCredentialsEmptyKeyValue() throws URISyntaxException, Inv CloudStorageAccount account2 = CloudStorageAccount.parse(emptyKeyConnectionString); assertEquals(emptyKeyConnectionString, account2.toString(true)); assertNotNull(account2.getCredentials()); - assertEquals(accountName, account2.getCredentials().getAccountName()); + assertEquals(ACCOUNT_NAME, account2.getCredentials().getAccountName()); assertEquals(emptyKeyValueAsString, Base64.encode(((StorageCredentialsAccountAndKey) (account2.getCredentials())).getCredentials() .exportKey())); - StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(accountName, accountKey); - assertEquals(accountName, credentials2.getAccountName()); - assertEquals(accountKey, Base64.encode(credentials2.getCredentials().exportKey())); + StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); + assertEquals(ACCOUNT_NAME, credentials2.getAccountName()); + assertEquals(ACCOUNT_KEY, Base64.encode(credentials2.getCredentials().exportKey())); byte[] emptyKeyValueAsByteArray = new byte[0]; - StorageCredentialsAccountAndKey credentials3 = new StorageCredentialsAccountAndKey(accountName, + StorageCredentialsAccountAndKey credentials3 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, emptyKeyValueAsByteArray); - assertEquals(accountName, credentials3.getAccountName()); + assertEquals(ACCOUNT_NAME, credentials3.getAccountName()); assertEquals(Base64.encode(emptyKeyValueAsByteArray), Base64.encode(credentials3.getCredentials().exportKey())); } @@ -123,20 +123,20 @@ public void testStorageCredentialsNullKeyValue() { String nullKeyValueAsString = null; try { - new StorageCredentialsAccountAndKey(accountName, nullKeyValueAsString); + new StorageCredentialsAccountAndKey(ACCOUNT_NAME, nullKeyValueAsString); fail("Did not hit expected exception"); } catch (NullPointerException ex) { // assertEquals(SR.KEY_NULL, ex.getMessage()); } - StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(accountName, accountKey); - assertEquals(accountName, credentials2.getAccountName()); - assertEquals(accountKey, Base64.encode(credentials2.getCredentials().exportKey())); + StorageCredentialsAccountAndKey credentials2 = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); + assertEquals(ACCOUNT_NAME, credentials2.getAccountName()); + assertEquals(ACCOUNT_KEY, Base64.encode(credentials2.getCredentials().exportKey())); byte[] nullKeyValueAsByteArray = null; try { - new StorageCredentialsAccountAndKey(accountName, nullKeyValueAsByteArray); + new StorageCredentialsAccountAndKey(ACCOUNT_NAME, nullKeyValueAsByteArray); fail("Did not hit expected exception"); } catch (IllegalArgumentException ex) { @@ -202,24 +202,24 @@ public void testCloudStorageAccountDevelopmentStorageAccount() throws InvalidKey } public void testCloudStorageAccountDefaultStorageAccountWithHttp() throws URISyntaxException, InvalidKeyException { - StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(accountName, accountKey); + StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(cred, false); assertEquals(cloudStorageAccount.getBlobEndpoint(), - new URI(String.format("http://%s.blob.core.windows.net", accountName))); + new URI(String.format("http://%s.blob.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getQueueEndpoint(), - new URI(String.format("http://%s.queue.core.windows.net", accountName))); + new URI(String.format("http://%s.queue.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getTableEndpoint(), - new URI(String.format("http://%s.table.core.windows.net", accountName))); + new URI(String.format("http://%s.table.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getFileEndpoint(), - new URI(String.format("http://%s.file.core.windows.net", accountName))); + new URI(String.format("http://%s.file.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getBlobStorageUri().getSecondaryUri(), - new URI(String.format("http://%s-secondary.blob.core.windows.net", accountName))); + new URI(String.format("http://%s-secondary.blob.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getQueueStorageUri().getSecondaryUri(), - new URI(String.format("http://%s-secondary.queue.core.windows.net", accountName))); + new URI(String.format("http://%s-secondary.queue.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getTableStorageUri().getSecondaryUri(), - new URI(String.format("http://%s-secondary.table.core.windows.net", accountName))); + new URI(String.format("http://%s-secondary.table.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getFileStorageUri().getSecondaryUri(), - new URI(String.format("http://%s-secondary.file.core.windows.net", accountName))); + new URI(String.format("http://%s-secondary.file.core.windows.net", ACCOUNT_NAME))); String cloudStorageAccountToStringWithSecrets = cloudStorageAccount.toString(true); CloudStorageAccount testAccount = CloudStorageAccount.parse(cloudStorageAccountToStringWithSecrets); @@ -228,24 +228,24 @@ public void testCloudStorageAccountDefaultStorageAccountWithHttp() throws URISyn } public void testCloudStorageAccountDefaultStorageAccountWithHttps() throws URISyntaxException, InvalidKeyException { - StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(accountName, accountKey); + StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(cred, true); assertEquals(cloudStorageAccount.getBlobEndpoint(), - new URI(String.format("https://%s.blob.core.windows.net", accountName))); + new URI(String.format("https://%s.blob.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getQueueEndpoint(), - new URI(String.format("https://%s.queue.core.windows.net", accountName))); + new URI(String.format("https://%s.queue.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getTableEndpoint(), - new URI(String.format("https://%s.table.core.windows.net", accountName))); + new URI(String.format("https://%s.table.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getFileEndpoint(), - new URI(String.format("https://%s.file.core.windows.net", accountName))); + new URI(String.format("https://%s.file.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getBlobStorageUri().getSecondaryUri(), - new URI(String.format("https://%s-secondary.blob.core.windows.net", accountName))); + new URI(String.format("https://%s-secondary.blob.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getQueueStorageUri().getSecondaryUri(), - new URI(String.format("https://%s-secondary.queue.core.windows.net", accountName))); + new URI(String.format("https://%s-secondary.queue.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getTableStorageUri().getSecondaryUri(), - new URI(String.format("https://%s-secondary.table.core.windows.net", accountName))); + new URI(String.format("https://%s-secondary.table.core.windows.net", ACCOUNT_NAME))); assertEquals(cloudStorageAccount.getFileStorageUri().getSecondaryUri(), - new URI(String.format("https://%s-secondary.file.core.windows.net", accountName))); + new URI(String.format("https://%s-secondary.file.core.windows.net", ACCOUNT_NAME))); String cloudStorageAccountToStringWithSecrets = cloudStorageAccount.toString(true); CloudStorageAccount testAccount = CloudStorageAccount.parse(cloudStorageAccountToStringWithSecrets); @@ -255,11 +255,11 @@ public void testCloudStorageAccountDefaultStorageAccountWithHttps() throws URISy public void testCloudStorageAccountConnectionStringRoundtrip() throws InvalidKeyException, URISyntaxException { String accountString1 = String.format("DefaultEndpointsProtocol=http;AccountName=%s;AccountKey=%s", - accountName, accountKey); + ACCOUNT_NAME, ACCOUNT_KEY); String accountString2 = String.format( - "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;QueueEndpoint=%s", accountName, - accountKey, "https://alternate.queue.endpoint/"); + "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;QueueEndpoint=%s", ACCOUNT_NAME, + ACCOUNT_KEY, "https://alternate.queue.endpoint/"); connectionStringRoundtripHelper(accountString1); connectionStringRoundtripHelper(accountString2); @@ -276,7 +276,7 @@ private void connectionStringRoundtripHelper(String accountString) throws Invali } public void testCloudStorageAccountClientMethods() throws URISyntaxException { - StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(accountName, accountKey); + StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); CloudStorageAccount account = new CloudStorageAccount(cred, false); CloudBlobClient blob = account.createCloudBlobClient(); @@ -304,7 +304,7 @@ public void testCloudStorageAccountClientMethods() throws URISyntaxException { } public void testCloudStorageAccountClientUriVerify() throws URISyntaxException, StorageException { - StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(accountName, accountKey); + StorageCredentialsAccountAndKey cred = new StorageCredentialsAccountAndKey(ACCOUNT_NAME, ACCOUNT_KEY); CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(cred, true); CloudBlobClient blobClient = cloudStorageAccount.createCloudBlobClient(); @@ -343,7 +343,8 @@ public void testCloudStorageAccountParseNullEmpty() throws InvalidKeyException, } } - public void testCloudStorageAccountDevStoreNonTrueFails() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountDevStoreFalseFails() + throws InvalidKeyException, URISyntaxException { try { CloudStorageAccount.parse("UseDevelopmentStorage=false"); fail(); @@ -353,7 +354,8 @@ public void testCloudStorageAccountDevStoreNonTrueFails() throws InvalidKeyExcep } } - public void testCloudStorageAccountDevStorePlusAccountFails() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountDevStoreFalsePlusAccountFails() + throws InvalidKeyException, URISyntaxException { try { CloudStorageAccount.parse("UseDevelopmentStorage=false;AccountName=devstoreaccount1"); fail(); @@ -363,10 +365,23 @@ public void testCloudStorageAccountDevStorePlusAccountFails() throws InvalidKeyE } } - public void testCloudStorageAccountDevStorePlusEndpointFails() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountDevStoreFalsePlusEndpointFails() + throws InvalidKeyException, URISyntaxException { + try { + CloudStorageAccount.parse("UseDevelopmentStorage=false;" + + "BlobEndpoint=http://127.0.0.1:1000/devstoreaccount1"); + fail(); + } + catch (IllegalArgumentException ex) { + assertEquals(SR.INVALID_CONNECTION_STRING_DEV_STORE_NOT_TRUE, ex.getMessage()); + } + } + + public void testCloudStorageAccountDevStoreFalsePlusEndpointSuffixFails() + throws InvalidKeyException, URISyntaxException { try { CloudStorageAccount - .parse("UseDevelopmentStorage=false;BlobEndpoint=http://127.0.0.1:1000/devstoreaccount1"); + .parse("UseDevelopmentStorage=false;EndpointSuffix=core.chinacloudapi.cn"); fail(); } catch (IllegalArgumentException ex) { @@ -423,47 +438,104 @@ public void testCloudStorageAccountDevStoreProxyUri() throws InvalidKeyException .getSecondaryUri()); } - public void testCloudStorageAccountDevStoreRoundtrip() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountDevStoreRoundtrip() + throws InvalidKeyException, URISyntaxException { String accountString = "UseDevelopmentStorage=true"; assertEquals(accountString, CloudStorageAccount.parse(accountString).toString(true)); } - public void testCloudStorageAccountDevStoreProxyRoundtrip() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountDevStoreProxyRoundtrip() + throws InvalidKeyException, URISyntaxException { String accountString = "UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://ipv4.fiddler/"; assertEquals(accountString, CloudStorageAccount.parse(accountString).toString(true)); } - public void testCloudStorageAccountDefaultCloudRoundtrip() throws InvalidKeyException, URISyntaxException { - String accountString = "DefaultEndpointsProtocol=http;AccountName=test;AccountKey=abc="; + public void testCloudStorageAccountDefaultCloudRoundtrip() + throws InvalidKeyException, URISyntaxException { + String accountString = "EndpointSuffix=a.b.c;DefaultEndpointsProtocol=http;AccountName=test;" + + "AccountKey=abc="; assertEquals(accountString, CloudStorageAccount.parse(accountString).toString(true)); } - public void testCloudStorageAccountExplicitCloudRoundtrip() throws InvalidKeyException, URISyntaxException { - String accountString = "BlobEndpoint=https://blobs/;AccountName=test;AccountKey=abc="; + public void testCloudStorageAccountExplicitCloudRoundtrip() + throws InvalidKeyException, URISyntaxException { + String accountString = "EndpointSuffix=a.b.c;BlobEndpoint=https://blobs/;AccountName=test;" + + "AccountKey=abc="; assertEquals(accountString, CloudStorageAccount.parse(accountString).toString(true)); } - public void testCloudStorageAccountAnonymousRoundtrip() throws InvalidKeyException, URISyntaxException { + public void testCloudStorageAccountAnonymousRoundtrip() + throws InvalidKeyException, URISyntaxException { String accountString = "BlobEndpoint=http://blobs/"; assertEquals(accountString, CloudStorageAccount.parse(accountString).toString(true)); - CloudStorageAccount account = new CloudStorageAccount(null, new StorageUri(new URI("http://blobs/")), null, - null, null); + CloudStorageAccount account = new CloudStorageAccount( + null, new StorageUri(new URI("http://blobs/")), null, null, null); AccountsAreEqual(account, CloudStorageAccount.parse(account.toString(true))); } + + public void testCloudStorageAccountInvalidAnonymousRoundtrip() + throws InvalidKeyException, URISyntaxException { + String accountString = "AccountKey=abc="; + try { + assertNull(CloudStorageAccount.parse(accountString)); + fail(); + } + catch (Exception ex) { + assertEquals(SR.INVALID_CONNECTION_STRING, ex.getMessage()); + } + } public void testCloudStorageAccountEmptyValues() throws InvalidKeyException, URISyntaxException { - String accountString = ";BlobEndpoint=http://blobs/;;AccountName=test;;AccountKey=abc=;"; - String validAccountString = "BlobEndpoint=http://blobs/;AccountName=test;AccountKey=abc="; + String accountString = ";EndpointSuffix=a.b.c;;BlobEndpoint=http://blobs/;;" + + "AccountName=test;;AccountKey=abc=;;;"; + String validAccountString = "EndpointSuffix=a.b.c;BlobEndpoint=http://blobs/;" + + "AccountName=test;AccountKey=abc="; assertEquals(validAccountString, CloudStorageAccount.parse(accountString).toString(true)); } + + public void testCloudStorageAccountEndpointSuffix() + throws InvalidKeyException, URISyntaxException, StorageException { + final String mooncake = "core.chinacloudapi.cn"; + final String fairfax = "core.usgovcloudapi.net"; + + // Endpoint suffix for mooncake + CloudStorageAccount accountParse = CloudStorageAccount.parse( + "DefaultEndpointsProtocol=http;AccountName=test;" + + "AccountKey=abc=;EndpointSuffix=" + mooncake); + CloudStorageAccount accountConstruct = new CloudStorageAccount(accountParse.getCredentials(), + false, accountParse.getEndpointSuffix()); + assertNotNull(accountParse); + assertNotNull(accountConstruct); + assertNotNull(accountParse.getBlobEndpoint()); + assertEquals(accountParse.getBlobEndpoint(), accountConstruct.getBlobEndpoint()); + assertTrue(accountParse.getBlobEndpoint().toString().endsWith(mooncake)); + + // Endpoint suffix for fairfax + accountParse = CloudStorageAccount.parse( + "TableEndpoint=http://tables/;DefaultEndpointsProtocol=http;" + + "AccountName=test;AccountKey=abc=;EndpointSuffix=" + fairfax); + accountConstruct = new CloudStorageAccount(accountParse.getCredentials(), + false, accountParse.getEndpointSuffix()); + assertNotNull(accountParse); + assertNotNull(accountConstruct); + assertNotNull(accountParse.getBlobEndpoint()); + assertEquals(accountParse.getBlobEndpoint(), accountConstruct.getBlobEndpoint()); + assertTrue(accountParse.getBlobEndpoint().toString().endsWith(fairfax)); + + // Explicit table endpoint should override endpoint suffix for fairfax + CloudTableClient tableClientParse = accountParse.createCloudTableClient(); + assertNotNull(tableClientParse); + assertEquals(accountParse.getTableEndpoint(), tableClientParse.getEndpoint()); + assertTrue(tableClientParse.getEndpoint().toString().endsWith("tables/")); + } public void testCloudStorageAccountJustBlobToString() throws InvalidKeyException, URISyntaxException { String accountString = "BlobEndpoint=http://blobs/;AccountName=test;AccountKey=abc="; 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 1f52af7..22dfaa6 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 @@ -200,7 +200,7 @@ public void testBlobTypesWithStorageUri() throws StorageException, URISyntaxExce assertEquals(containerUri, directory.getContainer().getStorageUri()); assertEquals(endpoint, directory.getServiceClient().getStorageUri()); - CloudBlobDirectory subdirectory = directory.getSubDirectoryReference("subdirectory"); + CloudBlobDirectory subdirectory = directory.getDirectoryReference("subdirectory"); assertEquals(subdirectoryUri, subdirectory.getStorageUri()); assertEquals(subdirectoryUri.getPrimaryUri(), subdirectory.getUri()); assertEquals(directoryUri, subdirectory.getParent().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 939ba85..17769f0 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 @@ -31,6 +31,7 @@ public class TestHelper { private static CloudStorageAccount account; + @SuppressWarnings("deprecation") private final static AuthenticationScheme defaultAuthenticationScheme = AuthenticationScheme.SHAREDKEYFULL; public static String connectionString; @@ -40,24 +41,28 @@ public class TestHelper { public static StorageUri queueEndpoint; public static StorageUri tableEndpoint; + @SuppressWarnings("deprecation") public static CloudBlobClient createCloudBlobClient() throws StorageException { CloudBlobClient client = getAccount().createCloudBlobClient(); client.setAuthenticationScheme(defaultAuthenticationScheme); return client; } - public static CloudFileClient createCloudFileClient() throws StorageException { + @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); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java index 7b5acb3..9ac002d 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java @@ -29,6 +29,7 @@ import com.microsoft.azure.storage.SendingRequestEvent; import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.core.SR; /** * Blob Client Tests @@ -41,7 +42,7 @@ public class CloudBlobClientTests extends TestCase { * @throws IOException * @throws InterruptedException */ - public void testListContainersTest() throws StorageException, URISyntaxException { + public void testListContainers() throws StorageException, URISyntaxException { CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient(); ArrayList containerList = new ArrayList(); String prefix = UUID.randomUUID().toString(); @@ -74,6 +75,53 @@ public void testListContainersTest() throws StorageException, URISyntaxException assertEquals(0, containerList.size()); } + + /** + * Try to list the containers to ensure maxResults validation is working. + * + * @throws StorageException + * @throws URISyntaxException + */ + public void testListContainersMaxResultsValidation() + throws StorageException, URISyntaxException { + CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient(); + String prefix = UUID.randomUUID().toString(); + + // Validation should cause each of these to fail. + for(int i = 0; i >= -2; i--) { + try { + bClient.listContainersSegmented( + prefix, ContainerListingDetails.ALL, i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(bClient.listContainersSegmented("thereshouldntbeanycontainersswiththisprefix")); + } + + /** + * Fetch result segments and ensure pageSize is null when unspecified and will cap at 5000. + * + * @throws StorageException + * @throws URISyntaxException + */ + public void testListContainersResultSegment() + throws StorageException, URISyntaxException { + CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient(); + + ResultSegment segment1 = bClient.listContainersSegmented(); + assertNotNull(segment1); + assertNull(segment1.getPageSize()); + + ResultSegment segment2 = bClient.listContainersSegmented(null, + ContainerListingDetails.ALL, 9001, null, null, null); + assertNotNull(segment2); + assertNotNull(segment2.getPageSize()); + assertEquals(5000, segment2.getPageSize().intValue()); + } public void testGetServiceStats() throws StorageException { CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient(); 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 9fcf67f..e56291d 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 @@ -105,7 +105,7 @@ public void testCloudBlobContainerReference() throws StorageException, URISyntax CloudBlockBlob blockBlob = container.getBlockBlobReference("directory1/blob1"); CloudPageBlob pageBlob = container.getPageBlobReference("directory2/blob2"); CloudBlobDirectory directory = container.getDirectoryReference("directory3"); - CloudBlobDirectory directory2 = directory.getSubDirectoryReference("directory4"); + CloudBlobDirectory directory2 = directory.getDirectoryReference("directory4"); assertEquals(container.getStorageUri().toString(), blockBlob.getContainer().getStorageUri().toString()); assertEquals(container.getStorageUri().toString(), pageBlob.getContainer().getStorageUri().toString()); @@ -432,6 +432,32 @@ public void testCloudBlobContainerListBlobs() throws StorageException, IOExcepti assertTrue(blobNames.size() == 0); } + + /** + * Try to list the blobs in a container to ensure maxResults validation is working. + * + * @throws URISyntaxException + * @throws StorageException + * @throws IOException + */ + public void testCloudBlobContainerListBlobsMaxResultsValidation() + throws StorageException, IOException, URISyntaxException { + this.container.create(); + + // Validation should cause each of these to fail. + for (int i = 0; i >= -2; i--) { + try { + this.container.listBlobsSegmented( + "bb", false, EnumSet.noneOf(BlobListingDetails.class), i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(this.container.listBlobsSegmented("thereshouldntbeanyblobswiththisprefix")); + } /** * List the blobs in a container 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 80efe76..9745114 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 @@ -496,7 +496,7 @@ private void testGetReferences(String delimiter, CloudBlobContainer container) t CloudBlobDirectory blockParent = blockBlob.getParent(); assertEquals("Dir1" + delimiter, blockParent.getPrefix()); - CloudBlobDirectory subDirectory = dir1.getSubDirectoryReference("SubDirectory"); + CloudBlobDirectory subDirectory = dir1.getDirectoryReference("SubDirectory"); assertEquals(dir1.getContainer().getName(), subDirectory.getContainer().getName()); assertEquals(dir1.getServiceClient().getEndpoint().toString(), subDirectory.getServiceClient().getEndpoint() .toString()); @@ -541,7 +541,7 @@ public void testGetSubdirectoryAndTraverseBackToParent() throws URISyntaxExcepti private void testGetSubdirectoryAndTraverseBackToParent(String delimiter, CloudBlobContainer container) throws URISyntaxException, StorageException { CloudBlobDirectory directory = container.getDirectoryReference("TopDir1" + delimiter); - CloudBlobDirectory subDirectory = directory.getSubDirectoryReference("MidDir1" + delimiter); + CloudBlobDirectory subDirectory = directory.getDirectoryReference("MidDir1" + delimiter); CloudBlobDirectory parent = subDirectory.getParent(); assertEquals(parent.getPrefix(), directory.getPrefix()); assertEquals(parent.getUri(), directory.getUri()); @@ -609,19 +609,19 @@ private void testHierarchicalTraversal(String delimiter, CloudBlobContainer cont // Traverse hierarchically starting with length 1 CloudBlobDirectory directory1 = container.getDirectoryReference("Dir1" + delimiter); - CloudBlobDirectory subdir1 = directory1.getSubDirectoryReference("Dir2"); + CloudBlobDirectory subdir1 = directory1.getDirectoryReference("Dir2"); CloudBlobDirectory parent1 = subdir1.getParent(); assertEquals(parent1.getPrefix(), directory1.getPrefix()); - CloudBlobDirectory subdir2 = subdir1.getSubDirectoryReference("Dir3"); + CloudBlobDirectory subdir2 = subdir1.getDirectoryReference("Dir3"); CloudBlobDirectory parent2 = subdir2.getParent(); assertEquals(parent2.getPrefix(), subdir1.getPrefix()); - CloudBlobDirectory subdir3 = subdir2.getSubDirectoryReference("Dir4"); + CloudBlobDirectory subdir3 = subdir2.getDirectoryReference("Dir4"); CloudBlobDirectory parent3 = subdir3.getParent(); assertEquals(parent3.getPrefix(), subdir2.getPrefix()); - CloudBlobDirectory subdir4 = subdir3.getSubDirectoryReference("Dir5"); + CloudBlobDirectory subdir4 = subdir3.getDirectoryReference("Dir5"); CloudBlobDirectory parent4 = subdir4.getParent(); assertEquals(parent4.getPrefix(), subdir3.getPrefix()); } @@ -692,12 +692,12 @@ private void testValidateInRootContainer(String delimiter) throws URISyntaxExcep assertEquals("", root.getPrefix()); assertEquals(container.getUri(), root.getUri()); - CloudBlobDirectory subdir1 = directory1.getSubDirectoryReference("MidDir" + delimiter); + CloudBlobDirectory subdir1 = directory1.getDirectoryReference("MidDir" + delimiter); CloudBlobDirectory parent1 = subdir1.getParent(); assertEquals(directory1.getPrefix(), parent1.getPrefix()); assertEquals(directory1.getUri(), parent1.getUri()); - CloudBlobDirectory subdir2 = subdir1.getSubDirectoryReference("EndDir" + delimiter); + CloudBlobDirectory subdir2 = subdir1.getDirectoryReference("EndDir" + delimiter); CloudBlobDirectory parent2 = subdir2.getParent(); assertEquals(subdir1.getPrefix(), parent2.getPrefix()); assertEquals(subdir1.getUri(), parent2.getUri()); @@ -733,10 +733,10 @@ private void testDelimitersInARow(String delimiter, CloudBlobContainer container // Traverse from root to leaf CloudBlobDirectory directory4 = container.getDirectoryReference(delimiter); - CloudBlobDirectory directory5 = directory4.getSubDirectoryReference(delimiter); + CloudBlobDirectory directory5 = directory4.getDirectoryReference(delimiter); assertEquals(delimiter + delimiter, directory5.getPrefix()); - CloudBlobDirectory directory6 = directory5.getSubDirectoryReference(delimiter); + CloudBlobDirectory directory6 = directory5.getDirectoryReference(delimiter); assertEquals(delimiter + delimiter + delimiter, directory6.getPrefix()); CloudPageBlob blob2 = directory6.getPageBlobReference("Blob1"); @@ -787,7 +787,7 @@ private void testMultipleDelimiters(String delimiter, CloudBlobContainer contain .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri()); CloudBlobDirectory directory = container.getDirectoryReference("TopDir1" + delimiter); - CloudBlobDirectory subDirectory = directory.getSubDirectoryReference("MidDir1" + delimiter); + CloudBlobDirectory subDirectory = directory.getDirectoryReference("MidDir1" + delimiter); CloudBlobDirectory parent = subDirectory.getParent(); assertEquals(parent.getPrefix(), directory.getPrefix()); assertEquals(parent.getUri(), directory.getUri()); 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 fe2f9f6..53579fe 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 @@ -76,7 +76,7 @@ public void testCloudBlobNameValidation() NameValidator.validateBlobName("4lphanum3r1c"); NameValidator.validateBlobName("CAPSLOCK"); NameValidator.validateBlobName("white space"); - NameValidator.validateBlobName("šth3r(h@racter$"); + NameValidator.validateBlobName("0th3r(h@racter$"); NameValidator.validateBlobName(new String(new char[253]).replace("\0", "a/a")); invalidBlobTestHelper("", "No empty strings.", "Invalid blob name. The name may not be null, empty, or whitespace only."); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java index 53ef9e1..c75f9ff 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java @@ -23,7 +23,7 @@ import com.microsoft.azure.storage.ResultContinuation; import com.microsoft.azure.storage.ResultSegment; import com.microsoft.azure.storage.StorageException; - +import com.microsoft.azure.storage.core.SR; /** * File Client Tests @@ -76,4 +76,29 @@ public void testListSharesTest() throws StorageException, URISyntaxException { } } } + + /** + * Tests doing a listShares to ensure maxResults validation is working. + * + * @throws StorageException + * @throws URISyntaxException + */ + public void testListSharesMaxResultsValidationTest() throws StorageException, URISyntaxException { + CloudFileClient fileClient = FileTestHelper.createCloudFileClient(); + String prefix = UUID.randomUUID().toString(); + + // Validation should cause each of these to fail + for (int i = 0; i >= -2; i--) { + try{ + fileClient.listSharesSegmented( + prefix, ShareListingDetails.ALL, i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(fileClient.listSharesSegmented("thereshouldntbeanyshareswiththisprefix")); + } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java index 8810d1f..0cb7d90 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java @@ -30,6 +30,7 @@ import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.core.PathUtility; +import com.microsoft.azure.storage.core.SR; /** * File Directory Tests @@ -88,15 +89,15 @@ private boolean CloudFileDirectorySetup(CloudFileShare share) throws URISyntaxEx try { CloudFileDirectory rootDirectory = share.getRootDirectoryReference(); for (int i = 1; i < 3; i++) { - CloudFileDirectory topDirectory = rootDirectory.getSubDirectoryReference("TopDir" + i); + CloudFileDirectory topDirectory = rootDirectory.getDirectoryReference("TopDir" + i); topDirectory.create(); for (int j = 1; j < 3; j++) { - CloudFileDirectory midDirectory = topDirectory.getSubDirectoryReference("MidDir" + j); + CloudFileDirectory midDirectory = topDirectory.getDirectoryReference("MidDir" + j); midDirectory.create(); for (int k = 1; k < 3; k++) { - CloudFileDirectory endDirectory = midDirectory.getSubDirectoryReference("EndDir" + k); + CloudFileDirectory endDirectory = midDirectory.getDirectoryReference("EndDir" + k); endDirectory.create(); CloudFile file1 = endDirectory.getFileReference("EndFile" + k); @@ -123,7 +124,7 @@ private boolean CloudFileDirectorySetup(CloudFileShare share) throws URISyntaxEx */ public void testCloudFileDirectoryConstructor() throws URISyntaxException, StorageException { CloudFileShare share = FileTestHelper.getRandomShareReference(); - CloudFileDirectory directory = share.getRootDirectoryReference().getSubDirectoryReference("directory1"); + CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory1"); CloudFileDirectory directory2 = new CloudFileDirectory(directory.getStorageUri(), null); assertEquals(directory.getName(), directory2.getName()); assertEquals(directory.getStorageUri(), directory2.getStorageUri()); @@ -138,7 +139,7 @@ public void testCloudFileDirectoryConstructor() throws URISyntaxException, Stora * @throws StorageException */ public void testCloudFileDirectoryCreateAndDelete() throws URISyntaxException, StorageException { - CloudFileDirectory directory = this.share.getRootDirectoryReference().getSubDirectoryReference("directory1"); + CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1"); directory.create(); assertTrue(directory.exists()); directory.delete(); @@ -152,7 +153,7 @@ public void testCloudFileDirectoryCreateAndDelete() throws URISyntaxException, S * @throws URISyntaxException */ public void testCloudFileDirectoryCreateIfNotExists() throws StorageException, URISyntaxException { - CloudFileDirectory directory = this.share.getRootDirectoryReference().getSubDirectoryReference("directory1"); + CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1"); assertTrue(directory.createIfNotExists()); assertFalse(directory.createIfNotExists()); directory.delete(); @@ -166,7 +167,7 @@ public void testCloudFileDirectoryCreateIfNotExists() throws StorageException, U * @throws StorageException */ public void testCloudFileDirectoryDeleteIfExists() throws URISyntaxException, StorageException { - CloudFileDirectory directory = this.share.getRootDirectoryReference().getSubDirectoryReference("directory1"); + CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1"); assertFalse(directory.deleteIfExists()); directory.create(); assertTrue(directory.deleteIfExists()); @@ -181,7 +182,7 @@ public void testCloudFileDirectoryDeleteIfExists() throws URISyntaxException, St */ public void testCloudFileDirectoryListFilesAndDirectories() throws StorageException, URISyntaxException { if (CloudFileDirectorySetup(this.share)) { - CloudFileDirectory topDir1 = this.share.getRootDirectoryReference().getSubDirectoryReference("TopDir1"); + CloudFileDirectory topDir1 = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); Iterable list1 = topDir1.listFilesAndDirectories(); assertTrue(list1.iterator().hasNext()); ArrayList simpleList1 = new ArrayList(); @@ -230,6 +231,35 @@ public void testCloudFileDirectoryListFilesAndDirectories() throws StorageExcept } } + /** + * Test listFilesAndDirectories for maxResults validation. + * + * @throws StorageException + * @throws URISyntaxException + */ + public void CloudFileDirectoryListFilesAndDirectoriesMaxResultsValidation() + throws StorageException, URISyntaxException { + if (CloudFileDirectorySetup(this.share)) { + CloudFileDirectory topDir = + this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); + + // Validation should cause each of these to fail + for (int i = 0; i >= -2; i--) { + try { + topDir.listFilesAndDirectoriesSegmented(i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(topDir.listFilesAndDirectoriesSegmented()); + } else { + fail(); + } + } + /** * Tests to make sure you can't delete a directory if it is nonempty. * @@ -238,7 +268,7 @@ public void testCloudFileDirectoryListFilesAndDirectories() throws StorageExcept */ public void testCloudFileDirectoryWithFilesDelete() throws URISyntaxException, StorageException { if (CloudFileDirectorySetup(this.share)) { - CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getSubDirectoryReference( + CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference( "TopDir1/MidDir1/EndDir1"); CloudFile file1 = dir1.getFileReference("EndFile1"); @@ -276,7 +306,7 @@ public void CloudFileDirectoryConditionalAccess() share.create(); if (CloudFileDirectorySetup(share)) { - CloudFileDirectory dir1 = share.getRootDirectoryReference().getSubDirectoryReference("TopDir1/MidDir1/EndDir1"); + CloudFileDirectory dir1 = share.getRootDirectoryReference().getDirectoryReference("TopDir1/MidDir1/EndDir1"); CloudFile file1 = dir1.getFileReference("EndFile1"); file1.delete(); dir1.FetchAttributes(); @@ -299,7 +329,7 @@ public void CloudFileDirectoryConditionalAccess() dir1.delete(AccessCondition.GenerateIfMatchCondition(etag), null); // LastModifiedTime tests - CloudFileDirectory dir2 = share.getRootDirectoryReference().getSubDirectoryReference("TopDir1/MidDir1/EndDir2"); + CloudFileDirectory dir2 = share.getRootDirectoryReference().getDirectoryReference("TopDir1/MidDir1/EndDir2"); CloudFile file2 = dir2.getFileReference("EndFile2"); file2.delete(); dir2.FetchAttributes(); @@ -342,7 +372,7 @@ public void CloudFileDirectoryConditionalAccess() * @throws StorageException */ public void testCloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxException, StorageException { - CloudFileDirectory dir = this.share.getRootDirectoryReference().getSubDirectoryReference("Dir1"); + CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); CloudFile file = dir.getFileReference("file1"); try { @@ -370,8 +400,8 @@ public void testCloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxE * @throws StorageException */ public void testCloudFileDirectoryCreateDirectoryWithoutParent() throws URISyntaxException, StorageException { - CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getSubDirectoryReference("Dir1"); - CloudFileDirectory dir2 = this.share.getRootDirectoryReference().getSubDirectoryReference("Dir1/Dir2"); + CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); + CloudFileDirectory dir2 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1/Dir2"); try { dir2.create(); fail("Directory shouldn't be created when the parent directory wasn't created."); @@ -393,7 +423,7 @@ public void testCloudFileDirectoryCreateDirectoryWithoutParent() throws URISynta * @throws URISyntaxException */ public void testCloudFileDirectoryGetParent() throws StorageException, URISyntaxException { - CloudFile file = this.share.getRootDirectoryReference().getSubDirectoryReference("Dir1") + CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("Dir1") .getFileReference("File1"); assertEquals("File1", file.getName()); @@ -426,8 +456,8 @@ public void testCloudFileDirectoryGetParent() throws StorageException, URISyntax * @throws StorageException */ public void testCloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() throws URISyntaxException, StorageException { - CloudFileDirectory directory = this.share.getRootDirectoryReference().getSubDirectoryReference("TopDir1"); - CloudFileDirectory subDirectory = directory.getSubDirectoryReference("MidDir1"); + CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); + CloudFileDirectory subDirectory = directory.getDirectoryReference("MidDir1"); CloudFileDirectory parent = subDirectory.getParent(); assertEquals(parent.getName(), directory.getName()); assertEquals(parent.getUri(), directory.getUri()); @@ -440,7 +470,7 @@ public void testCloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() throw * @throws StorageException */ public void testCloudFileDirectoryGetParentOnRoot() throws URISyntaxException, StorageException { - CloudFileDirectory root = this.share.getRootDirectoryReference().getSubDirectoryReference("TopDir1"); + CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); CloudFileDirectory parent = root.getParent(); assertNotNull(parent); @@ -456,20 +486,20 @@ public void testCloudFileDirectoryGetParentOnRoot() throws URISyntaxException, S */ public void testCloudFileDirectoryHierarchicalTraversal() throws URISyntaxException, StorageException { ////Traverse hierarchically starting with length 1 - CloudFileDirectory directory1 = this.share.getRootDirectoryReference().getSubDirectoryReference("Dir1"); - CloudFileDirectory subdir1 = directory1.getSubDirectoryReference("Dir2"); + CloudFileDirectory directory1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1"); + CloudFileDirectory subdir1 = directory1.getDirectoryReference("Dir2"); CloudFileDirectory parent1 = subdir1.getParent(); assertEquals(parent1.getName(), directory1.getName()); - CloudFileDirectory subdir2 = subdir1.getSubDirectoryReference("Dir3"); + CloudFileDirectory subdir2 = subdir1.getDirectoryReference("Dir3"); CloudFileDirectory parent2 = subdir2.getParent(); assertEquals(parent2.getName(), subdir1.getName()); - CloudFileDirectory subdir3 = subdir2.getSubDirectoryReference("Dir4"); + CloudFileDirectory subdir3 = subdir2.getDirectoryReference("Dir4"); CloudFileDirectory parent3 = subdir3.getParent(); assertEquals(parent3.getName(), subdir2.getName()); - CloudFileDirectory subdir4 = subdir3.getSubDirectoryReference("Dir5"); + CloudFileDirectory subdir4 = subdir3.getDirectoryReference("Dir5"); CloudFileDirectory parent4 = subdir4.getParent(); assertEquals(parent4.getName(), subdir3.getName()); } @@ -481,8 +511,8 @@ public void testCloudFileDirectoryHierarchicalTraversal() throws URISyntaxExcept * @throws StorageException */ public void testCloudFileDirectoryFileParentValidate() throws URISyntaxException, StorageException { - CloudFile file = this.share.getRootDirectoryReference().getSubDirectoryReference("TopDir1") - .getSubDirectoryReference("MidDir1").getSubDirectoryReference("EndDir1").getFileReference("EndFile1"); + CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1") + .getDirectoryReference("MidDir1").getDirectoryReference("EndDir1").getFileReference("EndFile1"); CloudFileDirectory directory = file.getParent(); assertEquals(directory.getName(), "EndDir1"); assertEquals(directory.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir1/EndDir1"); @@ -495,10 +525,9 @@ public void testCloudFileDirectoryFileParentValidate() throws URISyntaxException * @throws StorageException */ public void testCloudFileDirectoryGetEmptySubDirectory() throws URISyntaxException, StorageException { - CloudFileDirectory root = this.share.getRootDirectoryReference().getSubDirectoryReference("TopDir1"); - + CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1"); try { - root.getSubDirectoryReference(""); + root.getDirectoryReference(""); fail("Subdirectory references shouldn't work with empty strings."); } catch (IllegalArgumentException e) { @@ -513,12 +542,12 @@ public void testCloudFileDirectoryGetEmptySubDirectory() throws URISyntaxExcepti * @throws StorageException */ public void testCloudFileDirectoryAbsoluteUriAppended() throws URISyntaxException, StorageException { - CloudFileDirectory dir = this.share.getRootDirectoryReference().getSubDirectoryReference( + CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference( this.share.getUri().toString()); assertEquals(PathUtility.appendPathToUri(this.share.getStorageUri(), this.share.getUri().toString()) .getPrimaryUri(), dir.getUri()); assertEquals(new URI(this.share.getUri() + "/" + this.share.getUri()), dir.getUri()); - dir = this.share.getRootDirectoryReference().getSubDirectoryReference(this.share.getUri() + "/TopDir1"); + dir = this.share.getRootDirectoryReference().getDirectoryReference(this.share.getUri() + "/TopDir1"); assertEquals( PathUtility.appendPathToUri(this.share.getStorageUri(), this.share.getUri().toString() + "/TopDir1") .getPrimaryUri(), dir.getUri()); @@ -532,7 +561,7 @@ public void testCloudFileDirectoryAbsoluteUriAppended() throws URISyntaxExceptio * @throws IOException */ public void testCloudFileDirectoryDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException { - final CloudFileDirectory directory = this.share.getRootDirectoryReference().getSubDirectoryReference( + final CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference( "directory"); try { 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 aabff44..2515064 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 @@ -88,8 +88,8 @@ public void testCloudFileShareReference() throws StorageException, URISyntaxExce CloudFileClient client = FileTestHelper.createCloudFileClient(); CloudFileShare share = client.getShareReference("share"); - CloudFileDirectory directory = share.getRootDirectoryReference().getSubDirectoryReference("directory3"); - CloudFileDirectory directory2 = directory.getSubDirectoryReference("directory4"); + CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory3"); + CloudFileDirectory directory2 = directory.getDirectoryReference("directory4"); assertEquals(share.getStorageUri().toString(), directory.getShare().getStorageUri().toString()); assertEquals(share.getStorageUri().toString(), directory2.getShare().getStorageUri().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 0172163..1dcd8e6 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 @@ -1035,7 +1035,7 @@ public void testCloudFileDeleteIfExistsErrorCode() throws URISyntaxException, St CloudFileClient client = FileTestHelper.createCloudFileClient(); CloudFileShare share = client.getShareReference(FileTestHelper.generateRandomShareName()); share.create(); - CloudFileDirectory directory = share.getRootDirectoryReference().getSubDirectoryReference("directory"); + CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory"); directory.create(); final CloudFile file = directory.getFileReference("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 d92f6b8..5b5faa9 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 @@ -26,6 +26,7 @@ import com.microsoft.azure.storage.ResultSegment; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestHelper; +import com.microsoft.azure.storage.core.SR; public final class CloudQueueClientTests extends TestCase { @@ -130,12 +131,29 @@ public void testListQueuesSegmented() throws URISyntaxException, StorageExceptio assertTrue(totalRoundTrip == 7); - ResultSegment segment3 = qClient.listQueuesSegmented(prefix, QueueListingDetails.NONE, 0, null, - null, null); + ResultSegment segment3 = qClient.listQueuesSegmented(prefix, QueueListingDetails.NONE, + null, null, null, null); assertTrue(segment3.getLength() == 35); } - public void testListQueuesEqual() throws StorageException { + public void testListQueuesSegmentedMaxResultsValidation() throws URISyntaxException, StorageException { + CloudQueueClient qClient = QueueTestHelper.createCloudQueueClient(); + + // Validation should cause each of these to fail + for (int i = 0; i >= -2; i--) { + try { + qClient.listQueuesSegmented(null, QueueListingDetails.NONE, i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(qClient.listQueuesSegmented("thereshouldntbeanyqueueswiththisprefix")); + } + + public void testListQueuesEqual() throws StorageException { CloudQueueClient qClient = QueueTestHelper.createCloudQueueClient(); int count1 = 0; for (CloudQueue queue : qClient.listQueues()) { 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 bae8640..1a38b1f 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 @@ -42,25 +42,26 @@ import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageLocation; import com.microsoft.azure.storage.core.PathUtility; +import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.table.TableTestHelper.Class1; import com.microsoft.azure.storage.table.TableTestHelper.Class2; /** * Table Client Tests */ +@SuppressWarnings("deprecation") public class TableClientTests extends TestCase { - public void testListTablesSegmented() throws URISyntaxException, StorageException { TableRequestOptions options = new TableRequestOptions(); - - options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata); - testListTablesSegmented(options); - - options.setTablePayloadFormat(TablePayloadFormat.Json); - testListTablesSegmented(options); - - options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); - testListTablesSegmented(options); + TablePayloadFormat[] formats = + {TablePayloadFormat.JsonFullMetadata, + TablePayloadFormat.Json, + TablePayloadFormat.JsonNoMetadata}; + + for (TablePayloadFormat format : formats) { + options.setTablePayloadFormat(format); + testListTablesSegmented(options); + } } private void testListTablesSegmented(TableRequestOptions options) throws URISyntaxException, @@ -108,6 +109,24 @@ private void testListTablesSegmented(TableRequestOptions options) throws URISynt } } } + + public void testListTablesSegmentedMaxResultsValidation() + throws URISyntaxException, StorageException { + final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); + + // Validation should cause each of these to fail. + for (int i = 0; i >= -2; i--) { + try { + tClient.listTablesSegmented(null, i, null, null, null); + fail(); + } + catch (IllegalArgumentException e) { + assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) + .equals(e.getMessage())); + } + } + assertNotNull(tClient.listTablesSegmented("thereshouldntbeanytableswiththisprefix")); + } public void testListTablesSegmentedNoPrefix() throws URISyntaxException, StorageException { TableRequestOptions options = new TableRequestOptions(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableQueryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableQueryTests.java index 46557d9..821af11 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableQueryTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableQueryTests.java @@ -15,6 +15,7 @@ package com.microsoft.azure.storage.table; import java.net.HttpURLConnection; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -335,8 +336,262 @@ private void testTableQueryIterateTwice(TableRequestOptions options) { secondIteration.get(m).getProperties().get("D").getValueAsByteArray())); } } + + public void testTableQueryRoundTripDate() throws URISyntaxException, StorageException { + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate(new Date(1417943712123L)); - public void testTableQueryWithDynamicEntity() { + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate(new Date(1421247212800L)); + } + + public void testTableQueryRoundTripDateJson() throws URISyntaxException, StorageException { + // JSON + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, false, TablePayloadFormat.Json); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, false, TablePayloadFormat.Json); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, false, TablePayloadFormat.Json); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, false, TablePayloadFormat.Json); + + + // JSON NO METADATA + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, false, TablePayloadFormat.JsonNoMetadata); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, false, TablePayloadFormat.JsonNoMetadata); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, false, TablePayloadFormat.JsonNoMetadata); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, false, TablePayloadFormat.JsonNoMetadata); + } + + public void testTableQueryRoundTripDateJsonCrossVersion() + throws URISyntaxException, StorageException { + // JSON + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, false, TablePayloadFormat.Json); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, false, TablePayloadFormat.Json); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, false, TablePayloadFormat.Json); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, false, TablePayloadFormat.Json); + + // JSON NO METADATA + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, false, TablePayloadFormat.JsonNoMetadata); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, false, TablePayloadFormat.JsonNoMetadata); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, false, TablePayloadFormat.JsonNoMetadata); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, false, TablePayloadFormat.JsonNoMetadata); + } + + public void testTableQueryRoundTripDateJsonWithBackwardCompatibility() + throws URISyntaxException, StorageException { + // JSON + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, true, TablePayloadFormat.Json); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, true, TablePayloadFormat.Json); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, true, TablePayloadFormat.Json); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, true, TablePayloadFormat.Json); + + // JSON NO METADATA + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, true, TablePayloadFormat.JsonNoMetadata); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, true, TablePayloadFormat.JsonNoMetadata); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, true, TablePayloadFormat.JsonNoMetadata); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, true, TablePayloadFormat.JsonNoMetadata); + } + + public void testTableQueryRoundTripDateJsonCrossVersionWithBackwardCompatibility() + throws URISyntaxException, StorageException { + // JSON + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, true, TablePayloadFormat.Json); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, true, TablePayloadFormat.Json); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, true, TablePayloadFormat.Json); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, true, TablePayloadFormat.Json); + + // JSON NO METADATA + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate( + "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, true, TablePayloadFormat.JsonNoMetadata); + + // 2015-01-14T14:53:32.800Z from Java + testTableQueryRoundTripDate( + "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, true, TablePayloadFormat.JsonNoMetadata); + + // 2014-11-29T22:55:21.9876543Z from .Net + testTableQueryRoundTripDate( + "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, true, TablePayloadFormat.JsonNoMetadata); + + // 2015-02-14T03:11:13.0000229Z from .Net + testTableQueryRoundTripDate( + "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, true, TablePayloadFormat.JsonNoMetadata); + } + + private void testTableQueryRoundTripDate(final Date date) throws URISyntaxException, StorageException { + final String partitionKey = "partitionTest"; + + // DateBackwardCompatibility off + String rowKey = TableTestHelper.generateRandomKeyName(); + DateTestEntity entity = new DateTestEntity(partitionKey, rowKey); + entity.setDate(date); + + TableOperation put = TableOperation.insertOrReplace(entity); + TableQueryTests.table.execute(put); + + TableOperation get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class); + entity = TableQueryTests.table.execute(get).getResultAsType(); + assertEquals(date.getTime(), entity.getDate().getTime()); + + // DateBackwardCompatibility on + rowKey = TableTestHelper.generateRandomKeyName(); + entity = new DateTestEntity(partitionKey, rowKey); + entity.setDate(date); + + put = TableOperation.insertOrReplace(entity); + TableQueryTests.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class); + final TableRequestOptions options = new TableRequestOptions(); + options.setDateBackwardCompatibility(true); + entity = TableQueryTests.table.execute(get, options, null).getResultAsType(); + assertEquals(date.getTime(), entity.getDate().getTime()); + + // DateBackwardCompatibility off + final String dateKey = "date"; + final EntityProperty property = new EntityProperty(date); + rowKey = TableTestHelper.generateRandomKeyName(); + DynamicTableEntity dynamicEntity = new DynamicTableEntity(partitionKey, rowKey); + dynamicEntity.getProperties().put(dateKey, property); + + put = TableOperation.insertOrReplace(dynamicEntity); + TableQueryTests.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class); + dynamicEntity = TableQueryTests.table.execute(get).getResultAsType(); + assertEquals(date.getTime(), dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime()); + + // DateBackwardCompatibility on + rowKey = TableTestHelper.generateRandomKeyName(); + dynamicEntity = new DynamicTableEntity(partitionKey, rowKey); + dynamicEntity.getProperties().put(dateKey, property); + + put = TableOperation.insertOrReplace(dynamicEntity); + TableQueryTests.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class); + options.setDateBackwardCompatibility(true); + dynamicEntity = TableQueryTests.table.execute(get, options, null).getResultAsType(); + assertEquals(date.getTime(), dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime()); + } + + private void testTableQueryRoundTripDate(final String dateString, final long milliseconds, final int ticks, + final boolean writtenPre2, final boolean dateBackwardCompatibility, TablePayloadFormat format) + throws URISyntaxException, StorageException { + assertTrue(ticks >= 0); // ticks is non-negative + assertTrue(ticks <= 9999); // ticks do not overflow into milliseconds + final String partitionKey = "partitionTest"; + final String dateKey = "date"; + long expectedMilliseconds = milliseconds; + + if (dateBackwardCompatibility && (milliseconds % 1000 == 0) && (ticks < 1000)) { + // when no milliseconds are present dateBackwardCompatibility causes up to 3 digits of ticks + // to be read as milliseconds + expectedMilliseconds += ticks; + } else if (writtenPre2 && !dateBackwardCompatibility && (ticks == 0)) { + // without DateBackwardCompatibility, milliseconds stored by Java prior to 0.4.0 are lost + expectedMilliseconds -= expectedMilliseconds % 1000; + } + + // Create a property for how the service would store the dateString + EntityProperty property = new EntityProperty(dateString, EdmType.DATE_TIME); + String rowKey = TableTestHelper.generateRandomKeyName(); + DynamicTableEntity dynamicEntity = new DynamicTableEntity(partitionKey, rowKey); + dynamicEntity.getProperties().put(dateKey, property); + + // Add the entity to the table + TableOperation put = TableOperation.insertOrReplace(dynamicEntity); + TableQueryTests.table.execute(put); + + // Specify the options + TableRequestOptions options = new TableRequestOptions(); + options.setDateBackwardCompatibility(dateBackwardCompatibility); + options.setTablePayloadFormat(format); + + // Fetch the entity from the table + TableOperation get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class); + dynamicEntity = TableQueryTests.table.execute(get, options, null).getResultAsType(); + + // Ensure the date matches our expectations + assertEquals(expectedMilliseconds, dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime()); + } + + public void testTableQueryWithDynamicEntity() { TableRequestOptions options = new TableRequestOptions(); options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata); @@ -401,7 +656,7 @@ private void testTableQueryWithProjection(TableRequestOptions options, boolean u assertNotNull(ent.getRowKey()); assertNotNull(ent.getTimestamp()); - // Validate correct columsn returned. + // Validate correct column returned. assertEquals(ent.getA(), randEnt.getA()); assertEquals(ent.getB(), null); assertEquals(ent.getC(), randEnt.getC()); @@ -675,4 +930,24 @@ private void testTableQueryWithContinuation(TableRequestOptions options, boolean assertEquals(count, 200); } + + private static class DateTestEntity extends TableServiceEntity { + private Date value; + + @SuppressWarnings("unused") + public DateTestEntity() { + } + + public DateTestEntity(String partition, String key) { + super(partition, key); + } + + public Date getDate() { + return value; + } + + public void setDate(Date value) { + this.value = value; + } + } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTestHelper.java index 3168b22..c744062 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableTestHelper.java @@ -39,6 +39,10 @@ public static String generateRandomQueueName() { String queueName = "queue" + UUID.randomUUID().toString(); return queueName.replace("-", ""); } + + public static String generateRandomKeyName() { + return "key" + UUID.randomUUID().toString(); + } public static CloudTable getRandomTableReference() throws URISyntaxException, StorageException { String tableName = generateRandomTableName(); diff --git a/microsoft-azure-storage/AndroidManifest.xml b/microsoft-azure-storage/AndroidManifest.xml index b0f5fe5..2249728 100644 --- a/microsoft-azure-storage/AndroidManifest.xml +++ b/microsoft-azure-storage/AndroidManifest.xml @@ -11,7 +11,7 @@ + android:versionName="0.4.0" > diff --git a/microsoft-azure-storage/pom.xml b/microsoft-azure-storage/pom.xml index 098595d..c05f61e 100644 --- a/microsoft-azure-storage/pom.xml +++ b/microsoft-azure-storage/pom.xml @@ -10,7 +10,7 @@ 4.0.0 com.microsoft.azure.android azure-storage-android - 0.3.1 + 0.4.0 aar Microsoft Azure Storage Android Client SDK diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java index 927f4a4..2faf581 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/AuthenticationScheme.java @@ -16,7 +16,10 @@ /** * Specifies the authentication scheme that is used to sign HTTP requests. + * + * @deprecated as of 0.4.0. In the future, only SHAREDKEYFULL will be used. */ +@Deprecated public enum AuthenticationScheme { /** * Signs HTTP requests using the Shared Key Lite authentication scheme. 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 93645fd..a97a790 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java @@ -18,7 +18,8 @@ import java.net.URISyntaxException; import java.security.InvalidKeyException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; import com.microsoft.azure.storage.analytics.CloudAnalyticsClient; @@ -47,14 +48,24 @@ public final class CloudStorageAccount { protected static final String ACCOUNT_NAME_NAME = "AccountName"; /** - * The root blob storage DNS name. + * Represents the final terms of each root storage DNS name. */ - private static final String BLOB_BASE_DNS_NAME = "blob.core.windows.net"; - + private static final String DNS_NAME_FORMAT = "%s.%s"; + + /** + * Represents the root storage DNS name. + */ + private static final String DEFAULT_DNS = "core.windows.net"; + /** * The suffix appended to account in order to access secondary location for read only access. */ private static final String SECONDARY_LOCATION_ACCOUNT_SUFFIX = "-secondary"; + + /** + * Represents the setting name for a custom storage endpoint suffix. + */ + private static final String ENDPOINT_SUFFIX_NAME = "EndpointSuffix"; /** * Represents the setting name for a custom blob storage endpoint. @@ -74,8 +85,8 @@ public final class CloudStorageAccount { /** * The format string for the secondary endpoint. */ - private static final String DEVELOPMENT_STORAGE_SECONDARY_ENDPOINT_FORMAT = DEVELOPMENT_STORAGE_PRIMARY_ENDPOINT_FORMAT - + SECONDARY_LOCATION_ACCOUNT_SUFFIX; + private static final String DEVELOPMENT_STORAGE_SECONDARY_ENDPOINT_FORMAT = + DEVELOPMENT_STORAGE_PRIMARY_ENDPOINT_FORMAT + SECONDARY_LOCATION_ACCOUNT_SUFFIX; /** * The setting name for specifying a development storage proxy Uri. @@ -92,11 +103,6 @@ public final class CloudStorageAccount { */ private static final String DEVSTORE_ACCOUNT_NAME = "devstoreaccount1"; - /** - * Represents the root file DNS name. - */ - private static final String FILE_BASE_DNS_NAME = "file.core.windows.net"; - /** * Represents the setting name for a custom file endpoint. */ @@ -112,11 +118,6 @@ public final class CloudStorageAccount { */ private static final String SECONDARY_ENDPOINT_FORMAT = "%s://%s%s.%s"; - /** - * Represents the root queue DNS name. - */ - protected static final String QUEUE_BASE_DNS_NAME = "queue.core.windows.net"; - /** * Represents the setting name for a custom queue endpoint. */ @@ -127,11 +128,6 @@ public final class CloudStorageAccount { */ protected static final String SHARED_ACCESS_SIGNATURE_NAME = "SharedAccessSignature"; - /** - * Represents the root table storage DNS name. - */ - protected static final String TABLE_BASE_DNS_NAME = "table.core.windows.net"; - /** * Represents the setting name for a custom table storage endpoint. */ @@ -141,52 +137,7 @@ public final class CloudStorageAccount { * The setting name for using the development storage. */ private static final String USE_DEVELOPMENT_STORAGE_NAME = "UseDevelopmentStorage"; - - /** - * Gets the default {@link StorageUri} using specified service and settings. - * - * @param settings - * A java.util.HashMap of key/value pairs which represents the connection settings. - * @param serviceDNS - * A String that represents the service's base DNS name. - * @return The default {@link StorageUri}. - * @throws URISyntaxException - */ - private static StorageUri getDefaultStorageUri(final HashMap settings, final String serviceDNS) - throws URISyntaxException { - final String scheme = settings.get(CloudStorageAccount.DEFAULT_ENDPOINTS_PROTOCOL_NAME) != null ? settings - .get(CloudStorageAccount.DEFAULT_ENDPOINTS_PROTOCOL_NAME) : Constants.HTTP; - final String accountName = settings.get(CloudStorageAccount.ACCOUNT_NAME_NAME); - - return getDefaultStorageUri(scheme, accountName, serviceDNS); - } - - /** - * Gets the default {@link StorageUri} using the specified service, protocol and account name. - * - * @param scheme - * The protocol to use. - * @param accountName - * The name of the storage account. - * @param serviceDNS - * A String that represents the service's base DNS name. - * @return The default {@link StorageUri}. - */ - private static StorageUri getDefaultStorageUri(final String scheme, final String accountName, - final String serviceDNS) throws URISyntaxException { - if (Utility.isNullOrEmpty(scheme)) { - throw new IllegalArgumentException(SR.SCHEME_NULL_OR_EMPTY); - } - - if (Utility.isNullOrEmpty(accountName)) { - throw new IllegalArgumentException(SR.ACCOUNT_NAME_NULL_OR_EMPTY); - } - - return new StorageUri(new URI(String.format(PRIMARY_ENDPOINT_FORMAT, scheme, accountName, serviceDNS)), - new URI(String.format(SECONDARY_ENDPOINT_FORMAT, scheme, accountName, - SECONDARY_LOCATION_ACCOUNT_SUFFIX, serviceDNS))); - } - + /** * Returns a {@link CloudStorageAccount} object that represents the development storage credentials. Secondary * endpoints are enabled by default. @@ -285,7 +236,7 @@ public static CloudStorageAccount parse(final String connectionString) throws UR } // 1. Parse connection string in to key / value pairs - final HashMap settings = Utility.parseAccountString(connectionString); + final Map settings = Utility.parseAccountString(connectionString); // 2 Validate General Settings rules, // - only setting value per key @@ -300,31 +251,112 @@ public static CloudStorageAccount parse(final String connectionString) throws UR } // 3. Validate scenario specific constraints - CloudStorageAccount retVal = tryConfigureDevStore(settings); - if (retVal != null) { - return retVal; + CloudStorageAccount account = tryConfigureDevStore(settings); + if (account != null) { + return account; } - retVal = tryConfigureServiceAccount(settings); - if (retVal != null) { - return retVal; + account = tryConfigureServiceAccount(settings); + if (account != null) { + return account; } throw new IllegalArgumentException(SR.INVALID_CONNECTION_STRING); } + + /** + * Gets the {@link StorageUri} using specified service, settings, and endpoint. + * + * @param settings + * A java.util.Map of key/value pairs which represents + * the connection settings. + * @param service + * A String that represents the service's base DNS name. + * @param serviceEndpoint + * The service endpoint name to check in settings. + * @return The {@link StorageUri}. + * @throws URISyntaxException + */ + private static StorageUri getStorageUri(final Map settings, + final String service, final String serviceEndpoint) throws URISyntaxException { + + // Explicit Endpoint Case + if (settings.containsKey(serviceEndpoint)) { + return new StorageUri(new URI(settings.get(serviceEndpoint))); + } + // Automatic Endpoint Case + else if (settings.containsKey(DEFAULT_ENDPOINTS_PROTOCOL_NAME) && + settings.containsKey(CloudStorageAccount.ACCOUNT_NAME_NAME) && + settings.containsKey(CloudStorageAccount.ACCOUNT_KEY_NAME)) { + final String scheme = settings.get(CloudStorageAccount.DEFAULT_ENDPOINTS_PROTOCOL_NAME); + final String accountName = settings.get(CloudStorageAccount.ACCOUNT_NAME_NAME); + final String endpointSuffix = settings.get(CloudStorageAccount.ENDPOINT_SUFFIX_NAME); + return getDefaultStorageUri(scheme, accountName, getDNS(service, endpointSuffix)); + } + // Otherwise + else { + return null; + } + } + + /** + * Gets the default {@link StorageUri} using the specified service, protocol and account name. + * + * @param scheme + * The protocol to use. + * @param accountName + * The name of the storage account. + * @param service + * A String that represents the service's base DNS name. + * @return The default {@link StorageUri}. + */ + private static StorageUri getDefaultStorageUri(final String scheme, final String accountName, + final String service) throws URISyntaxException { + if (Utility.isNullOrEmpty(scheme)) { + throw new IllegalArgumentException(SR.SCHEME_NULL_OR_EMPTY); + } + + if (Utility.isNullOrEmpty(accountName)) { + throw new IllegalArgumentException(SR.ACCOUNT_NAME_NULL_OR_EMPTY); + } + URI primaryUri = new URI(String.format( + PRIMARY_ENDPOINT_FORMAT, scheme, accountName, service)); + URI secondaryUri = new URI(String.format( + SECONDARY_ENDPOINT_FORMAT,scheme, accountName, + SECONDARY_LOCATION_ACCOUNT_SUFFIX, service)); + return new StorageUri(primaryUri, secondaryUri); + } + + /** + * This generates a domain name for the given service. + * + * @param service + * the service to connect to + * @param base + * the suffix to use + * @return the domain name + */ + private static String getDNS(String service, String base) { + if (base == null) { + base = DEFAULT_DNS; + } + + return String.format(DNS_NAME_FORMAT, service, base); + } + /** * Evaluates connection settings and returns a CloudStorageAccount representing Development Storage. * * @param settings - * A java.util.HashMap of key/value pairs which represents the connection settings. + * A java.util.Map of key/value pairs which represents the connection settings. * @return A {@link CloudStorageAccount} object constructed from the values provided in the connection settings, or * null if * one cannot be constructed. * @throws URISyntaxException * if the connection settings contains an invalid URI */ - private static CloudStorageAccount tryConfigureDevStore(final HashMap settings) + private static CloudStorageAccount tryConfigureDevStore(final Map settings) throws URISyntaxException { if (settings.containsKey(USE_DEVELOPMENT_STORAGE_NAME)) { if (!Boolean.parseBoolean(settings.get(USE_DEVELOPMENT_STORAGE_NAME))) { @@ -347,20 +379,18 @@ private static CloudStorageAccount tryConfigureDevStore(final HashMapjava.util.HashMap of key/value pairs which represents the connection settings. + * A java.util.Map of key/value pairs which represents + * the connection settings. * @return A {@link CloudStorageAccount} represented by the settings. * @throws URISyntaxException * if the connectionString specifies an invalid URI. * @throws InvalidKeyException * if credentials in the connection settings contain an invalid key. */ - private static CloudStorageAccount tryConfigureServiceAccount(final HashMap settings) + private static CloudStorageAccount tryConfigureServiceAccount(final Map settings) throws URISyntaxException, InvalidKeyException { - if (settings.containsKey(USE_DEVELOPMENT_STORAGE_NAME)) { - final String useDevStoreSetting = settings.get(USE_DEVELOPMENT_STORAGE_NAME); - - if (!Boolean.parseBoolean(useDevStoreSetting)) { + if (!Boolean.parseBoolean(settings.get(USE_DEVELOPMENT_STORAGE_NAME))) { throw new IllegalArgumentException(SR.INVALID_CONNECTION_STRING_DEV_STORE_NOT_TRUE); } else { @@ -368,54 +398,49 @@ private static CloudStorageAccount tryConfigureServiceAccount(final HashMapCloudStorageAccount class using the specified account credentials. + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials. *

- * With this constructor, the CloudStorageAccount object is constructed using the default HTTP storage - * service endpoints. The default HTTP storage service endpoints are + * With this constructor, the CloudStorageAccount object is constructed using the + * default HTTP storage service endpoints. The default HTTP storage service endpoints are * http://myaccount.blob.core.windows.net, * http://myaccount.queue.core.windows.net, * http://myaccount.table.core.windows.net, and - * http://myaccount.file.core.windows.net, where myaccount is the name of - * your storage account. + * http://myaccount.file.core.windows.net, where + * myaccount is the name of your storage account. + *

+ * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. + * A client object may be a {@link CloudBlobClient} object. + * + * @param storageCredentials + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. + * + * @throws URISyntaxException + * If storageCredentials specify an invalid account name. + */ + public CloudStorageAccount(final StorageCredentials storageCredentials) + throws URISyntaxException { + // Protocol defaults to HTTP unless otherwise specified + this(storageCredentials, false, null); + } + + /** + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials and the default service endpoints, using HTTP or HTTPS as specified. + *

+ * With this constructor, the CloudStorageAccount object is constructed using + * the default storage service endpoints. The default storage service endpoints are: + * [http|https]://myaccount.blob.core.windows.net; + * [http|https]://myaccount.queue.core.windows.net; + * [http|https]://myaccount.table.core.windows.net; and + * [http|https]://myaccount.file.core.windows.net, + * where myaccount is the name of your storage account. Access to the cloud + * storage account may be via HTTP or HTTPS, as specified by the useHttps parameter. + *

+ * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. A client + * object may be a {@link CloudBlobClient} object. + * + * @param storageCredentials + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. + * @param useHttps + * true to use HTTPS to connect to the storage service endpoints; + * otherwise, false. + * + * @throws URISyntaxException + * If storageCredentials specify an invalid account name. + */ + public CloudStorageAccount(final StorageCredentials storageCredentials, + final boolean useHttps) throws URISyntaxException { + this (storageCredentials, useHttps, null); + } + + /** + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials. *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. + * With this constructor, the CloudStorageAccount object is constructed using the + * given HTTP storage service endpoint suffix (if any, otherwise the default is used). + * + * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. + * A client object may be a {@link CloudBlobClient} object. * * @param storageCredentials - * A {@link StorageCredentials} object that represents the storage credentials to use to authenticate - * this account. + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. + * @param useHttps + * true to use HTTPS to connect to the storage service endpoints; + * otherwise, false. + * @param endpointSuffix + * A String that represents the endpointSuffix to use, if any. * * @throws URISyntaxException * If storageCredentials specify an invalid account name. */ - public CloudStorageAccount(final StorageCredentials storageCredentials) throws URISyntaxException { + public CloudStorageAccount(final StorageCredentials storageCredentials, + final boolean useHttps, final String endpointSuffix) throws URISyntaxException { Utility.assertNotNull("storageCredentials", storageCredentials); + String protocol = useHttps ? Constants.HTTPS : Constants.HTTP; + this.credentials = storageCredentials; - + this.blobStorageUri = getDefaultStorageUri(protocol, storageCredentials.getAccountName(), + getDNS(SR.BLOB, endpointSuffix)); + this.fileStorageUri = getDefaultStorageUri(protocol, storageCredentials.getAccountName(), + getDNS(SR.FILE, endpointSuffix)); + this.queueStorageUri = getDefaultStorageUri(protocol, storageCredentials.getAccountName(), + getDNS(SR.QUEUE, endpointSuffix)); + this.tableStorageUri = getDefaultStorageUri(protocol, storageCredentials.getAccountName(), + getDNS(SR.TABLE, endpointSuffix)); + this.endpointSuffix = endpointSuffix; + this.isBlobEndpointDefault = true; this.isFileEndpointDefault = true; this.isQueueEndpointDefault = true; this.isTableEndpointDefault = true; - - this.blobStorageUri = getDefaultStorageUri(Constants.HTTP, this.credentials.getAccountName(), - BLOB_BASE_DNS_NAME); - this.fileStorageUri = getDefaultStorageUri(Constants.HTTP, this.credentials.getAccountName(), - FILE_BASE_DNS_NAME); - this.queueStorageUri = getDefaultStorageUri(Constants.HTTP, this.credentials.getAccountName(), - QUEUE_BASE_DNS_NAME); - this.tableStorageUri = getDefaultStorageUri(Constants.HTTP, this.credentials.getAccountName(), - TABLE_BASE_DNS_NAME); } - + /** - * Creates an instance of the CloudStorageAccount class using the specified account credentials and - * service endpoints. + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials and service endpoints. *

- * Use this constructor to construct a CloudStorageAccount object using custom endpoints, in the case - * where you've configured a custom domain name for your storage account. + * Use this constructor to construct a CloudStorageAccount object using custom + * endpoints, in the case where you've configured a custom domain name for your storage account. *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. + * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. A + * client object may be a {@link CloudBlobClient} object. * * @param storageCredentials - * A {@link StorageCredentials} object that represents the storage credentials to use to authenticate - * this account. + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. * @param blobEndpoint * A java.net.URI object that represents the Blob service endpoint. * @param queueEndpoint @@ -536,24 +630,25 @@ public CloudStorageAccount(final StorageCredentials storageCredentials) throws U */ public CloudStorageAccount(final StorageCredentials storageCredentials, final URI blobEndpoint, final URI queueEndpoint, final URI tableEndpoint) { - this(storageCredentials, new StorageUri(blobEndpoint), new StorageUri(queueEndpoint), new StorageUri( - tableEndpoint), null); + this(storageCredentials, new StorageUri(blobEndpoint), new StorageUri(queueEndpoint), + new StorageUri(tableEndpoint), null); } /** - * Creates an instance of the CloudStorageAccount class using the specified account credentials and - * service endpoints. + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials and service endpoints. *

- * Use this constructor to construct a CloudStorageAccount object using custom endpoints, in the case - * where you've configured a custom domain name for your storage account. + * Use this constructor to construct a CloudStorageAccount object using custom + * endpoints, in the case where you've configured a custom domain name for your storage account. *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. + * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. A client + * object may be a {@link CloudBlobClient} object. * * @param storageCredentials - * A {@link StorageCredentials} object that represents the storage credentials to use to authenticate - * this account. + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. * @param blobEndpoint * A java.net.URI object that represents the Blob service endpoint. * @param queueEndpoint @@ -565,24 +660,25 @@ public CloudStorageAccount(final StorageCredentials storageCredentials, final UR */ public CloudStorageAccount(final StorageCredentials storageCredentials, final URI blobEndpoint, final URI queueEndpoint, final URI tableEndpoint, final URI fileEndpoint) { - this(storageCredentials, new StorageUri(blobEndpoint), new StorageUri(queueEndpoint), new StorageUri( - tableEndpoint), new StorageUri(fileEndpoint)); + this(storageCredentials, new StorageUri(blobEndpoint), new StorageUri(queueEndpoint), + new StorageUri(tableEndpoint), new StorageUri(fileEndpoint)); } /** - * Creates an instance of the CloudStorageAccount class using the specified account credentials and - * service endpoints. + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials and service endpoints. *

- * Use this constructor to construct a CloudStorageAccount object using custom endpoints, in the case - * where you've configured a custom domain name for your storage account. + * Use this constructor to construct a CloudStorageAccount object using custom + * endpoints, in the case where you've configured a custom domain name for your storage account. *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. + * The credentials provided when constructing the CloudStorageAccount object + * are used to authenticate all further requests against resources that are accessed via + * the CloudStorageAccount object or a client object created from it. A client + * object may be a {@link CloudBlobClient} object. * * @param storageCredentials - * A {@link StorageCredentials} object that represents the storage credentials to use to authenticate - * this account. + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. * @param blobStorageUri * A {@link StorageUri} object that represents the Blob service endpoint. * @param queueStorageUri @@ -590,25 +686,28 @@ public CloudStorageAccount(final StorageCredentials storageCredentials, final UR * @param tableStorageUri * A {@link StorageUri} object that represents the Table service endpoint. */ - public CloudStorageAccount(final StorageCredentials storageCredentials, final StorageUri blobStorageUri, - final StorageUri queueStorageUri, final StorageUri tableStorageUri) { + public CloudStorageAccount(final StorageCredentials storageCredentials, + final StorageUri blobStorageUri, + final StorageUri queueStorageUri, + final StorageUri tableStorageUri) { this(storageCredentials, blobStorageUri, queueStorageUri, tableStorageUri, null); } /** - * Creates an instance of the CloudStorageAccount class using the specified account credentials and - * service endpoints. + * Creates an instance of the CloudStorageAccount class using the specified + * account credentials and service endpoints. *

- * Use this constructor to construct a CloudStorageAccount object using custom endpoints, in the case - * where you've configured a custom domain name for your storage account. + * Use this constructor to construct a CloudStorageAccount object using custom + * endpoints, in the case where you've configured a custom domain name for your storage account. *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. + * The credentials provided when constructing the CloudStorageAccount object are + * used to authenticate all further requests against resources that are accessed via the + * CloudStorageAccount object or a client object created from it. + * A client object may be a {@link CloudBlobClient} object. * * @param storageCredentials - * A {@link StorageCredentials} object that represents the storage credentials to use to authenticate - * this account. + * A {@link StorageCredentials} object that represents the storage credentials + * to use to authenticate this account. * @param blobStorageUri * A {@link StorageUri} object that represents the Blob service endpoint. * @param queueStorageUri @@ -618,61 +717,18 @@ public CloudStorageAccount(final StorageCredentials storageCredentials, final St * @param fileStorageUri * A {@link StorageUri} object that represents the File service endpoint. */ - public CloudStorageAccount(final StorageCredentials storageCredentials, final StorageUri blobStorageUri, - final StorageUri queueStorageUri, final StorageUri tableStorageUri, final StorageUri fileStorageUri) { + public CloudStorageAccount( + final StorageCredentials storageCredentials, final StorageUri blobStorageUri, + final StorageUri queueStorageUri, final StorageUri tableStorageUri, + final StorageUri fileStorageUri) { this.credentials = storageCredentials; this.blobStorageUri = blobStorageUri; this.fileStorageUri = fileStorageUri; this.queueStorageUri = queueStorageUri; this.tableStorageUri = tableStorageUri; + this.endpointSuffix = null; } - - /** - * Creates an instance of the CloudStorageAccount class using the specified account credentials and the - * default service endpoints, using HTTP or HTTPS as specified. - *

- * With this constructor, the CloudStorageAccount object is constructed using the default storage - * service endpoints. The default storage service endpoints are - * [http|https]://myaccount.blob.core.windows.net; - * [http|https]://myaccount.queue.core.windows.net; - * [http|https]://myaccount.table.core.windows.net; and - * [http|https]://myaccount.file.core.windows.net, where myaccount is the - * name of your storage account. Access to the cloud storage account may be via HTTP or HTTPS, as specified by the - * useHttps parameter. - *

- * The credentials provided when constructing the CloudStorageAccount object are used to authenticate - * all further requests against resources that are accessed via the CloudStorageAccount object or a - * client object created from it. A client object may be a {@link CloudBlobClient} object. - * - * @param storageCredentials - * A {@link StorageCredentialsAccountAndKey} object that represents the storage credentials to use to - * authenticate this account. - * @param useHttps - * true to use HTTPS to connect to the storage service endpoints; otherwise, - * false. - * - * @throws URISyntaxException - * If storageCredentials specify an invalid account name. - */ - public CloudStorageAccount(final StorageCredentialsAccountAndKey storageCredentials, final boolean useHttps) - throws URISyntaxException { - Utility.assertNotNull("storageCredentials", storageCredentials); - - this.credentials = storageCredentials; - this.blobStorageUri = getDefaultStorageUri(useHttps ? Constants.HTTPS : Constants.HTTP, - storageCredentials.getAccountName(), BLOB_BASE_DNS_NAME); - this.fileStorageUri = getDefaultStorageUri(useHttps ? Constants.HTTPS : Constants.HTTP, - storageCredentials.getAccountName(), FILE_BASE_DNS_NAME); - this.queueStorageUri = getDefaultStorageUri(useHttps ? Constants.HTTPS : Constants.HTTP, - storageCredentials.getAccountName(), QUEUE_BASE_DNS_NAME); - this.tableStorageUri = getDefaultStorageUri(useHttps ? Constants.HTTPS : Constants.HTTP, - storageCredentials.getAccountName(), TABLE_BASE_DNS_NAME); - this.isBlobEndpointDefault = true; - this.isFileEndpointDefault = true; - this.isQueueEndpointDefault = true; - this.isTableEndpointDefault = true; - } - + /** * Creates a new Analytics service client. * @@ -808,6 +864,23 @@ public StorageUri getBlobStorageUri() { return this.blobStorageUri; } + /** + * Returns the credentials for the storage account. + * + * @return A {@link StorageCredentials} object that represents the credentials for this storage account. + */ + public StorageCredentials getCredentials() { + return this.credentials; + } + + /** + * If an endpoint suffix was specified, return it + * @return the endpoint suffix + */ + public String getEndpointSuffix() { + return this.endpointSuffix; + } + /** * Returns the endpoint for the File service for the storage account. This method is not supported when using shared * access signature credentials. @@ -840,15 +913,6 @@ public StorageUri getFileStorageUri() { return this.fileStorageUri; } - /** - * Returns the credentials for the storage account. - * - * @return A {@link StorageCredentials} object that represents the credentials for this storage account. - */ - public StorageCredentials getCredentials() { - return this.credentials; - } - /** * Returns the endpoint for the Queue service for the storage account. * @@ -909,24 +973,6 @@ public StorageUri getTableStorageUri() { return this.tableStorageUri; } - // - // Sets the StorageCredentials to use with this account. Warning internal - // use only, updating the credentials to a new account can potentially - // invalidate a bunch of pre-existing objects. - // - // @param credentials - // the credentials to set - // - /** - * Reserved for internal use. - * - * @param credentials - * Reserved. - */ - protected void setCredentials(final StorageCredentials credentials) { - this.credentials = credentials; - } - /** * Returns a connection string for this storage account, without sensitive data. * @@ -941,32 +987,39 @@ public String toString() { /** * Returns a connection string for this storage account, optionally with sensitive data. * - * @return A String that represents the connection string for this storage account, optionally with - * sensitive data. * @param exportSecrets - * true to include sensitive data in the string; otherwise, false. + * true to include sensitive data in the string; + * otherwise, false. + * @return A String that represents the connection string for this storage account, + * optionally with sensitive data. */ public String toString(final boolean exportSecrets) { if (this.credentials != null && Utility.isNullOrEmpty(this.credentials.getAccountName())) { return this.credentials.toString(exportSecrets); } - final ArrayList retVals = new ArrayList(); + final List values = new ArrayList(); if (this.isDevStoreAccount) { - retVals.add(String.format("%s=true", USE_DEVELOPMENT_STORAGE_NAME)); + values.add(String.format("%s=true", USE_DEVELOPMENT_STORAGE_NAME)); if (!this.getBlobEndpoint().toString().equals("http://127.0.0.1:10000/devstoreaccount1")) { - retVals.add(String.format("%s=%s://%s/", DEVELOPMENT_STORAGE_PROXY_URI_NAME, this.getBlobEndpoint() - .getScheme(), this.getBlobEndpoint().getHost())); + values.add(String.format("%s=%s://%s/", DEVELOPMENT_STORAGE_PROXY_URI_NAME, + this.getBlobEndpoint().getScheme(), this.getBlobEndpoint().getHost())); } } else { + final String attributeFormat = "%s=%s"; boolean addDefault = false; + + if (this.endpointSuffix != null) { + values.add(String.format(attributeFormat, ENDPOINT_SUFFIX_NAME, this.endpointSuffix)); + } + if (this.getBlobStorageUri() != null) { if (this.isBlobEndpointDefault) { addDefault = true; } else { - retVals.add(String.format("%s=%s", BLOB_ENDPOINT_NAME, this.getBlobEndpoint())); + values.add(String.format(attributeFormat, BLOB_ENDPOINT_NAME, this.getBlobEndpoint())); } } @@ -975,7 +1028,7 @@ public String toString(final boolean exportSecrets) { addDefault = true; } else { - retVals.add(String.format("%s=%s", QUEUE_ENDPOINT_NAME, this.getQueueEndpoint())); + values.add(String.format(attributeFormat, QUEUE_ENDPOINT_NAME, this.getQueueEndpoint())); } } @@ -984,7 +1037,7 @@ public String toString(final boolean exportSecrets) { addDefault = true; } else { - retVals.add(String.format("%s=%s", TABLE_ENDPOINT_NAME, this.getTableEndpoint())); + values.add(String.format(attributeFormat, TABLE_ENDPOINT_NAME, this.getTableEndpoint())); } } @@ -993,30 +1046,42 @@ public String toString(final boolean exportSecrets) { addDefault = true; } else { - retVals.add(String.format("%s=%s", FILE_ENDPOINT_NAME, this.getFileEndpoint())); + values.add(String.format(attributeFormat, FILE_ENDPOINT_NAME, this.getFileEndpoint())); } } if (addDefault) { - retVals.add(String.format("%s=%s", DEFAULT_ENDPOINTS_PROTOCOL_NAME, this.getBlobEndpoint().getScheme())); + values.add(String.format(attributeFormat, DEFAULT_ENDPOINTS_PROTOCOL_NAME, + this.getBlobEndpoint().getScheme())); } if (this.getCredentials() != null) { - retVals.add(this.getCredentials().toString(exportSecrets)); + values.add(this.getCredentials().toString(exportSecrets)); } } final StringBuilder returnString = new StringBuilder(); - for (final String val : retVals) { + for (final String val : values) { returnString.append(val); returnString.append(';'); } // Remove trailing ';' - if (retVals.size() > 0) { + if (values.size() > 0) { returnString.deleteCharAt(returnString.length() - 1); } return returnString.toString(); } + + /** + * Sets the StorageCredentials to use with this account. Warning: for internal use only, + * as updating the credentials to a new account can invalidate pre-existing objects. + * + * @param credentials + * the credentials to set + */ + protected void setCredentials(final StorageCredentials credentials) { + this.credentials = credentials; + } } 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 71c69f9..26a2577 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -515,7 +515,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "0.3.1"; + public static final String USER_AGENT_VERSION = "0.4.0"; /** * The default type for content-type and accept diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/ResultSegment.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/ResultSegment.java index f652c2a..b828bfa 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/ResultSegment.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ResultSegment.java @@ -36,7 +36,7 @@ public class ResultSegment { /** * Holds the size of the requested page. */ - private final int pageSize; + private final Integer pageSize; /** * Holds the ArrayList of results. @@ -53,7 +53,7 @@ public class ResultSegment { * @param token * A {@link ResultContinuation} object that represents the continuation token. */ - public ResultSegment(final ArrayList results, final int pageSize, final ResultContinuation token) { + public ResultSegment(final ArrayList results, final Integer pageSize, final ResultContinuation token) { this.results = results; this.length = results.size(); this.pageSize = pageSize; @@ -84,7 +84,7 @@ public boolean getHasMoreResults() { * @return true if the page has more results; otherwise, false. */ public boolean getIsPageComplete() { - return this.length == this.pageSize; + return (new Integer(this.length)).equals(this.pageSize); } /** @@ -101,7 +101,7 @@ public int getLength() { * * @return The size of the requested page. */ - public int getPageSize() { + public Integer getPageSize() { return this.pageSize; } 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 9c67908..25fc514 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java @@ -49,6 +49,7 @@ public abstract class ServiceClient { /** * Holds the AuthenticationScheme associated with this Service Client. */ + @SuppressWarnings("deprecation") protected AuthenticationScheme authenticationScheme = AuthenticationScheme.SHAREDKEYFULL; /** @@ -177,7 +178,10 @@ public final StorageCredentials getCredentials() { * * @return An {@link AuthenticationScheme} object which represents the authentication scheme associated with this * client. + * + * @deprecated as of 0.4.0. In the future only SharedKeyFull will be used. */ + @Deprecated public final AuthenticationScheme getAuthenticationScheme() { return this.authenticationScheme; } @@ -235,7 +239,10 @@ protected final void setStorageUri(final StorageUri storageUri) { * @param scheme * An {@link AuthenticationScheme} object which represents the authentication scheme being assigned for * the service client. + * + * @deprecated as of 0.4.0. In the future, only SharedKeyFull will be used. */ + @Deprecated public final void setAuthenticationScheme(final AuthenticationScheme scheme) { this.authenticationScheme = scheme; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessPolicyHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessPolicyHandler.java index 0a81b54..b0d1bbf 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessPolicyHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/SharedAccessPolicyHandler.java @@ -109,17 +109,17 @@ else if (Constants.ID.equals(currentNode)) { } else if (Constants.START.equals(currentNode)) { try { - this.policy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(value)); + this.policy.setSharedAccessStartTime(Utility.parseDate(value)); } - catch (ParseException e) { + catch (IllegalArgumentException e) { throw new SAXException(e); } } else if (Constants.EXPIRY.equals(currentNode)) { try { - this.policy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(value)); + this.policy.setSharedAccessExpiryTime(Utility.parseDate(value)); } - catch (ParseException e) { + catch (IllegalArgumentException e) { throw new SAXException(e); } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java index e91dc65..59459e2 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java @@ -17,7 +17,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.security.InvalidKeyException; -import java.util.HashMap; +import java.util.Map; import com.microsoft.azure.storage.core.Base64; import com.microsoft.azure.storage.core.SR; @@ -33,7 +33,7 @@ public abstract class StorageCredentials { * Tries to determine the storage credentials from a collection of name/value pairs. * * @param settings - * A HashMap object of the name/value pairs that represent the settings to use to configure + * A Map object of the name/value pairs that represent the settings to use to configure * the credentials. *

* Either include an account name with an account key (specifying values for @@ -49,7 +49,7 @@ public abstract class StorageCredentials { * If the key value specified for {@link CloudStorageAccount#ACCOUNT_KEY_NAME} is not a valid * Base64-encoded string. */ - protected static StorageCredentials tryParseCredentials(final HashMap settings) + protected static StorageCredentials tryParseCredentials(final Map settings) throws InvalidKeyException { final String accountName = settings.get(CloudStorageAccount.ACCOUNT_NAME_NAME) != null ? settings .get(CloudStorageAccount.ACCOUNT_NAME_NAME) : null; 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 8ce17da..79755cd 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 @@ -3005,11 +3005,6 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op accessCondition, blob.properties); } - @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 { 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 a9575c8..ffed262 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 @@ -217,7 +217,7 @@ public Iterable listContainers(final String prefix, */ @DoesServiceRequest public ResultSegment listContainersSegmented() throws StorageException { - return this.listContainersSegmented(null, ContainerListingDetails.NONE, 0, null /* continuationToken */, + return this.listContainersSegmented(null, ContainerListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -237,7 +237,7 @@ public ResultSegment listContainersSegmented() throws Storag */ @DoesServiceRequest public ResultSegment listContainersSegmented(final String prefix) throws StorageException { - return this.listContainersWithPrefixSegmented(prefix, ContainerListingDetails.NONE, 0, + return this.listContainersWithPrefixSegmented(prefix, ContainerListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -251,18 +251,19 @@ public ResultSegment listContainersSegmented(final String pr * @param detailsIncluded * A {@link ContainerListingDetails} value that indicates whether container metadata will be returned. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token returned + * by a previous listing operation. * @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 {@link ResultSegment} object that contains a segment of the enumerable collection of * {@link CloudBlobContainer} objects that represent the containers for this Blob service client. @@ -272,7 +273,7 @@ public ResultSegment listContainersSegmented(final String pr */ @DoesServiceRequest public ResultSegment listContainersSegmented(final String prefix, - final ContainerListingDetails detailsIncluded, final int maxResults, + final ContainerListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, final BlobRequestOptions options, final OperationContext opContext) throws StorageException { @@ -313,7 +314,7 @@ private Iterable listContainersWithPrefix(final String prefi SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable( - this.listContainersWithPrefixSegmentedImpl(prefix, detailsIncluded, -1, options, segmentedRequest), + this.listContainersWithPrefixSegmentedImpl(prefix, detailsIncluded, null, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), opContext); } @@ -327,18 +328,19 @@ private Iterable listContainersWithPrefix(final String prefi * @param detailsIncluded * A {@link ContainerListingDetails} value that indicates whether container metadata will be returned. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token returned + * by a previous listing operation. * @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 {@link ResultSegment} object that contains a segment of the enumerable collection of * {@link CloudBlobContainer} objects that represent the containers for this client. @@ -347,7 +349,7 @@ private Iterable listContainersWithPrefix(final String prefi * If a storage service error occurred. */ private ResultSegment listContainersWithPrefixSegmented(final String prefix, - final ContainerListingDetails detailsIncluded, final int maxResults, + final ContainerListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -367,7 +369,7 @@ private ResultSegment listContainersWithPrefixSegmented(fina } private StorageRequest> listContainersWithPrefixSegmentedImpl( - final String prefix, final ContainerListingDetails detailsIncluded, final int maxResults, + final String prefix, final ContainerListingDetails detailsIncluded, final Integer maxResults, final BlobRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.CONTAINER); @@ -423,7 +425,7 @@ public ResultSegment postProcessResponse(HttpURLConnection c } final ResultSegment resSegment = new ResultSegment( - response.getResults(), maxResults, newToken); + response.getResults(), response.getMaxResults(), newToken); // Important for listContainers because this is required by the lazy iterator between executions. segmentedRequest.setToken(resSegment.getContinuationToken()); 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 e5b2a25..1952bf6 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 @@ -1102,7 +1102,7 @@ public Iterable listBlobs(final String prefix, final boolean useFl SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable( - this.listBlobsSegmentedImpl(prefix, useFlatBlobListing, listingDetails, -1, options, segmentedRequest), + this.listBlobsSegmentedImpl(prefix, useFlatBlobListing, listingDetails, null, options, segmentedRequest), this.blobServiceClient, this, options.getRetryPolicyFactory(), opContext); } @@ -1117,7 +1117,7 @@ public Iterable listBlobs(final String prefix, final boolean useFl */ @DoesServiceRequest public ResultSegment listBlobsSegmented() throws StorageException { - return this.listBlobsSegmented(null, false, EnumSet.noneOf(BlobListingDetails.class), -1, null, null, null); + return this.listBlobsSegmented(null, false, EnumSet.noneOf(BlobListingDetails.class), null, null, null, null); } /** @@ -1135,46 +1135,48 @@ public ResultSegment listBlobsSegmented() throws StorageException */ @DoesServiceRequest public ResultSegment listBlobsSegmented(final String prefix) throws StorageException { - return this.listBlobsSegmented(prefix, false, EnumSet.noneOf(BlobListingDetails.class), -1, null, null, null); + return this.listBlobsSegmented(prefix, false, EnumSet.noneOf(BlobListingDetails.class), null, null, null, null); } /** - * Returns a result segment containing a collection of blob items whose names begin with the specified prefix, using - * the specified flat or hierarchical option, listing details options, request options, and operation context. + * Returns a result segment containing a collection of blob items whose names begin with the + * specified prefix, using the specified flat or hierarchical option, listing details options, + * request options, and operation context. * * @param prefix * A String that represents the prefix of the blob name. * @param useFlatBlobListing - * true to indicate that the returned list will be flat; false to indicate that - * the returned list will be hierarchical. + * true to indicate that the returned list will be flat; + * false to indicate that the returned list will be hierarchical. * @param listingDetails - * A java.util.EnumSet object that contains {@link BlobListingDetails} values that indicate - * whether snapshots, metadata, and/or uncommitted blocks are returned. Committed blocks are always - * returned. + * A java.util.EnumSet object that contains {@link BlobListingDetails} + * values that indicate whether snapshots, metadata, and/or uncommitted blocks + * are returned. Committed blocks are always returned. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token returned + * by a previous listing operation. * @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 {@link ResultSegment} object that contains a segment of the enumerable collection of - * {@link ListBlobItem} objects that represent the block items whose names begin with the specified prefix - * in the container. + * @return A {@link ResultSegment} object that contains a segment of the enumerable collection + * of {@link ListBlobItem} objects that represent the block items whose names begin + * with the specified prefix in the container. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public ResultSegment listBlobsSegmented(final String prefix, final boolean useFlatBlobListing, - final EnumSet listingDetails, final int maxResults, + final EnumSet listingDetails, final Integer maxResults, final ResultContinuation continuationToken, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -1200,7 +1202,7 @@ public ResultSegment listBlobsSegmented(final String prefix, final private StorageRequest> listBlobsSegmentedImpl( final String prefix, final boolean useFlatBlobListing, final EnumSet listingDetails, - final int maxResults, final BlobRequestOptions options, final SegmentedStorageRequest segmentedRequest) { + final Integer maxResults, final BlobRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.BLOB); Utility.assertNotNull("options", options); @@ -1258,7 +1260,7 @@ public ResultSegment postProcessResponse(HttpURLConnection connect } final ResultSegment resSegment = new ResultSegment(response.getResults(), - maxResults, newToken); + response.getMaxResults(), newToken); // Important for listBlobs because this is required by the lazy iterator between executions. segmentedRequest.setToken(resSegment.getContinuationToken()); @@ -1361,31 +1363,32 @@ public ResultSegment listContainersSegmented(final String pr } /** - * Returns a result segment containing a collection of containers whose names begin with the specified prefix for - * the service client associated with this container, using the specified listing details options, request options, - * and operation context. + * Returns a result segment containing a collection of containers whose names begin with + * the specified prefix for the service client associated with this container, + * using the specified listing details options, request options, and operation context. * * @param prefix * A String that represents the prefix of the container name. * @param detailsIncluded * A {@link ContainerListingDetails} object that indicates whether metadata is included. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token + * returned by a previous listing operation. * @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 {@link ResultSegment} object that contains a segment of the enumerable collection of - * {@link CloudBlobContainer} objects that represent the containers whose names begin with the specified - * prefix for the service client associated with this container. + * @return A {@link ResultSegment} object that contains a segment of the enumerable collection + * of {@link CloudBlobContainer} objects that represent the containers whose names + * begin with the specified prefix for the service client associated with this container. * * @throws StorageException * If a storage service error occurred. @@ -1393,7 +1396,7 @@ public ResultSegment listContainersSegmented(final String pr */ @DoesServiceRequest public ResultSegment listContainersSegmented(final String prefix, - final ContainerListingDetails detailsIncluded, final int maxResults, + final ContainerListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, final BlobRequestOptions options, final OperationContext opContext) throws StorageException { return this.blobServiceClient.listContainersSegmented(prefix, detailsIncluded, maxResults, continuationToken, 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 06d2db9..444655d 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 @@ -269,7 +269,7 @@ public CloudBlobClient getServiceClient() { * @throws URISyntaxException * If the resource URI is invalid. */ - public CloudBlobDirectory getSubDirectoryReference(String directoryName) throws URISyntaxException { + public CloudBlobDirectory getDirectoryReference(String directoryName) throws URISyntaxException { Utility.assertNotNullOrEmpty("directoryName", directoryName); if (!directoryName.endsWith(this.blobServiceClient.getDirectoryDelimiter())) { @@ -282,6 +282,24 @@ public CloudBlobDirectory getSubDirectoryReference(String directoryName) throws 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 0.4.0. Use {@link #getDirectoryReference()} instead. + */ + @Deprecated + public CloudBlobDirectory getSubDirectoryReference(String directoryName) throws URISyntaxException { + return this.getDirectoryReference(directoryName); + } /** * Returns the URI for this directory. 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 f9e1086..a72e7cc 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 @@ -459,7 +459,10 @@ public static void signRequestForBlobAndQueue(final HttpURLConnection request, f * @throws InvalidKeyException * if the credentials key is invalid. * @throws StorageException + * + * @deprecated as of 0.4.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 { @@ -523,7 +526,10 @@ public static void signRequestForTableSharedKey(final HttpURLConnection request, * @throws InvalidKeyException * if the credentials key is invalid. * @throws StorageException + * + * @deprecated as of 0.4.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()); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListResponse.java index 98ba9ec..c7337c3 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListResponse.java @@ -36,7 +36,7 @@ public class ListResponse { /** * Stores the max results. */ - protected int maxResults; + protected Integer maxResults; /** * Stores the next marker. @@ -71,7 +71,7 @@ public String getMarker() { * * @return the max results */ - public int getMaxResults() { + public Integer getMaxResults() { return this.maxResults; } @@ -119,7 +119,7 @@ public void setMarker(String marker) { * @param maxResults * the maxResults to set */ - public void setMaxResults(int maxResults) { + public void setMaxResults(Integer maxResults) { this.maxResults = maxResults; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListingContext.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListingContext.java index ccf7e06..7d9817a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListingContext.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ListingContext.java @@ -14,6 +14,8 @@ */ package com.microsoft.azure.storage.core; +import com.microsoft.azure.storage.core.Utility; + /** * RESERVED FOR INTERNAL USE. A class which holds the current context of a listing */ @@ -79,9 +81,13 @@ public final void setMarker(final String marker) { /** * @param maxResults - * the maxResults to set + * the maxResults to set, must be at least 1 */ protected final void setMaxResults(final Integer maxResults) { + if (null != maxResults) { + Utility.assertGreaterThanOrEqual("maxResults", maxResults, 1); + } + this.maxResults = maxResults; } 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 9f0b8c1..5e90c4c 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 @@ -115,6 +115,7 @@ public class SR { 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."; + public static final String PARAMETER_SHOULD_BE_GREATER_OR_EQUAL = "The value of the parameter '%s' should be greater than or equal to %s."; public static final String PARTITIONKEY_MISSING_FOR_DELETE = "Delete requires a partition key."; public static final String PARTITIONKEY_MISSING_FOR_MERGE = "Merge requires a partition key."; public static final String PARTITIONKEY_MISSING_FOR_UPDATE = "Replace requires a partition key."; 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 2ec3ec3..bc2c2bd 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 @@ -53,7 +53,12 @@ public static boolean canCredentialsSignRequest(final StorageCredentials creds) // @return true if a request can be signed with these // credentials; otherwise, false // - /** Reserved. */ + /** + * Reserved. + * + * @deprecated as of 0.4.0. Use {@link #canCredentialsSignRequest} instead. + */ + @Deprecated public static boolean canCredentialsSignRequestLite(final StorageCredentials creds) { if (creds.getClass().equals(StorageCredentialsAccountAndKey.class)) { return true; @@ -163,7 +168,10 @@ public static void signBlobAndQueueRequest(final StorageCredentials creds, * If the given key is invalid. * @throws StorageException * If an unspecified storage exception occurs. + * + * @deprecated as of 0.4.0. Use {@link #signBlobAndQueueRequest} instead. */ + @Deprecated public static void signBlobAndQueueRequestLite(final StorageCredentials creds, final java.net.HttpURLConnection request, final long contentLength) throws InvalidKeyException, StorageException { @@ -186,7 +194,10 @@ public static void signBlobAndQueueRequestLite(final StorageCredentials creds, * If the given key is invalid. * @throws StorageException * If a storage service error occurred. + * + * @deprecated as of 0.4.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 { @@ -253,7 +264,10 @@ public static void signTableRequest(final StorageCredentials creds, final java.n * If the given key is invalid. * @throws StorageException * If an unspecified storage exception occurs. + * + * @deprecated as of 0.4.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); @@ -275,7 +289,10 @@ public static void signTableRequestLite(final StorageCredentials creds, final ja * If the given key is invalid. * @throws StorageException * If a storage service error occurred. + * + * @deprecated as of 0.4.0. Use {@link #signTableRequest} instead. */ + @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)) { 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 22d0b7f..cc0f9d4 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 @@ -41,6 +41,7 @@ * @param * The type of the expected result */ +@SuppressWarnings("deprecation") public abstract class StorageRequest { /** * Holds a reference to a realized exception which occurred during execution. 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 53bc079..ed5d053 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 @@ -85,20 +85,21 @@ public final class Utility { private static final String RFC1123_GMT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; /** - * Stores a reference to the ISO8061 date/time pattern. + * Stores a reference to the ISO8601 date/time pattern. */ - public static final String ISO8061_PATTERN_NO_SECONDS = "yyyy-MM-dd'T'HH:mm'Z'"; + private static final String ISO8601_PATTERN_NO_SECONDS = "yyyy-MM-dd'T'HH:mm'Z'"; /** - * Stores a reference to the ISO8061 date/time pattern. + * Stores a reference to the ISO8601 date/time pattern. */ - public static final String ISO8061_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; /** - * Stores a reference to the ISO8061_LONG date/time pattern. + * Stores a reference to the Java version of ISO8601_LONG date/time pattern. The full version cannot be used + * because Java Dates have millisecond precision. */ - public static final String ISO8061_LONG_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"; - + private static final String JAVA_ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + /** * List of ports used for path style addressing. */ @@ -108,8 +109,18 @@ public final class Utility { /** * A factory to create SAXParser instances. */ - private final static SAXParserFactory factory = SAXParserFactory.newInstance(); + private static final SAXParserFactory factory = SAXParserFactory.newInstance(); + /** + * Stores a reference to the date/time pattern with the greatest precision Java.util.Date is capable of expressing. + */ + private static final String MAX_PRECISION_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + + /** + * The length of a datestring that matches the MAX_PRECISION_PATTERN. + */ + private static final int MAX_PRECISION_DATESTRING_LENGTH = MAX_PRECISION_PATTERN.replaceAll("'", "").length(); + /** * * Determines the size of an input stream, and optionally calculates the MD5 hash for the stream. @@ -327,7 +338,7 @@ public static void assertInBounds(final String param, final long value, final lo */ public static void assertGreaterThanOrEqual(final String param, final long value, final long min) { if (value < min) { - throw new IllegalArgumentException(String.format(SR.PARAMETER_SHOULD_BE_GREATER, param, min)); + throw new IllegalArgumentException(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, param, min)); } } @@ -484,38 +495,48 @@ public static byte[] getBytesFromLong(final long value) { } /** - * Returns the current GMT date/time using the RFC1123 pattern. + * Returns the current GMT date/time String using the RFC1123 pattern. * * @return A String that represents the current GMT date/time using the RFC1123 pattern. */ public static String getGMTTime() { - final DateFormat rfc1123Format = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US); - rfc1123Format.setTimeZone(GMT_ZONE); - return rfc1123Format.format(new Date()); - } - - public static String getTimeByZoneAndFormat(Date date, TimeZone zone, String format) { - final DateFormat formatter = new SimpleDateFormat(format, LOCALE_US); - formatter.setTimeZone(zone); - return formatter.format(date); + return getGMTTime(new Date()); } /** - * Returns the GTM date/time for the specified value using the RFC1123 pattern. + * Returns the GTM date/time String for the specified value using the RFC1123 pattern. * - * @param inDate + * @param date * A Date object that represents the date to convert to GMT date/time in the RFC1123 * pattern. * * @return A String that represents the GMT date/time for the specified value using the RFC1123 * pattern. */ - public static String getGMTTime(final Date inDate) { - final DateFormat rfc1123Format = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US); - rfc1123Format.setTimeZone(GMT_ZONE); - return rfc1123Format.format(inDate); + public static String getGMTTime(final Date date) { + final DateFormat formatter = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US); + formatter.setTimeZone(GMT_ZONE); + return formatter.format(date); } + + /** + * Returns the UTC date/time String for the specified value using Java's version of the ISO8601 pattern, + * which is limited to millisecond precision. + * + * @param date + * A Date object that represents the date to convert to UTC date/time in Java's version + * of the ISO8601 pattern. + * + * @return A String that represents the UTC date/time for the specified value using Java's version + * of the ISO8601 pattern. + */ + public static String getJavaISO8601Time(Date date) { + final DateFormat formatter = new SimpleDateFormat(JAVA_ISO8601_PATTERN, LOCALE_US); + formatter.setTimeZone(UTC_ZONE); + return formatter.format(date); + } + /** * Returns a namespace aware SAXParser. * @@ -528,7 +549,7 @@ public static SAXParser getSAXParser() throws ParserConfigurationException, SAXE factory.setNamespaceAware(true); return factory.newSAXParser(); } - + /** * Returns the standard header value from the specified connection request, or an empty string if no header value * has been specified for the request. @@ -549,13 +570,13 @@ public static String getStandardHeaderValue(final HttpURLConnection conn, final } /** - * Returns the UTC date/time for the specified value using the ISO8061 pattern. + * Returns the UTC date/time for the specified value using the ISO8601 pattern. * * @param value - * A Date object that represents the date to convert to UTC date/time in the ISO8061 + * A Date object that represents the date to convert to UTC date/time in the ISO8601 * pattern. If this value is null, this method returns an empty string. * - * @return A String that represents the UTC date/time for the specified value using the ISO8061 + * @return A String that represents the UTC date/time for the specified value using the ISO8601 * pattern, or an empty string if value is null. */ public static String getUTCTimeOrEmpty(final Date value) { @@ -563,10 +584,10 @@ public static String getUTCTimeOrEmpty(final Date value) { return Constants.EMPTY_STRING; } - final DateFormat iso8061Format = new SimpleDateFormat(ISO8061_PATTERN, LOCALE_US); - iso8061Format.setTimeZone(UTC_ZONE); + final DateFormat iso8601Format = new SimpleDateFormat(ISO8601_PATTERN, LOCALE_US); + iso8601Format.setTimeZone(UTC_ZONE); - return iso8061Format.format(value); + return iso8601Format.format(value); } /** @@ -676,28 +697,13 @@ public static HashMap parseAccountString(final String parseStrin */ public static Date parseDateFromString(final String value, final String pattern, final TimeZone timeZone) throws ParseException { - final DateFormat format = new SimpleDateFormat(pattern, Utility.LOCALE_US); - format.setTimeZone(timeZone); - return format.parse(value); - } - - /** - * Returns a date in the ISO8061 long pattern for the specified string. - * - * @param value - * A String that represents the string to parse. - * - * @return A Date object that represents the date in the ISO8061 long pattern. - * - * @throws ParseException - * If the specified string is invalid. - */ - public static Date parseISO8061LongDateFromString(final String value) throws ParseException { - return parseDateFromString(value, ISO8061_LONG_PATTERN, Utility.UTC_ZONE); - } + final DateFormat rfc1123Format = new SimpleDateFormat(pattern, Utility.LOCALE_US); + rfc1123Format.setTimeZone(timeZone); + return rfc1123Format.parse(value); + } /** - * Returns a GMT date in the RFC1123 pattern for the specified string. + * Returns a GMT date for the specified string in the RFC1123 pattern. * * @param value * A String that represents the string to parse. @@ -708,7 +714,9 @@ public static Date parseISO8061LongDateFromString(final String value) throws Par * If the specified string is invalid. */ public static Date parseRFC1123DateFromStringInGMT(final String value) throws ParseException { - return parseDateFromString(value, RFC1123_GMT_PATTERN, Utility.GMT_ZONE); + final DateFormat format = new SimpleDateFormat(RFC1123_GMT_PATTERN, Utility.LOCALE_US); + format.setTimeZone(GMT_ZONE); + return format.parse(value); } /** @@ -1081,7 +1089,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr } /** - * Private Default Ctor. + * Private Default Constructor. */ private Utility() { // No op @@ -1101,52 +1109,86 @@ public static void checkNullaryCtor(Class clazzType) { } } + /** + * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it + * with up to millisecond precision. + * + * @param dateString + * the String to be interpreted as a Date + * + * @return the corresponding Date object + */ public static Date parseDate(String dateString) { - try { - if (dateString.length() == 28) { - // "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"-> [2012-01-04T23:21:59.1234567Z] length = 28 - return Utility.parseDateFromString(dateString, Utility.ISO8061_LONG_PATTERN, Utility.UTC_ZONE); - } - else if (dateString.length() == 20) { - // "yyyy-MM-dd'T'HH:mm:ss'Z'"-> [2012-01-04T23:21:59Z] length = 20 - return Utility.parseDateFromString(dateString, Utility.ISO8061_PATTERN, Utility.UTC_ZONE); - } - else if (dateString.length() == 17) { - // "yyyy-MM-dd'T'HH:mm'Z'"-> [2012-01-04T23:21Z] length = 17 - return Utility.parseDateFromString(dateString, Utility.ISO8061_PATTERN_NO_SECONDS, Utility.UTC_ZONE); - } - else if (dateString.length() == 27) { - // "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"-> [2012-01-04T23:21:59.123456Z] length = 27 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Utility.UTC_ZONE); - } - else if (dateString.length() == 26) { - // "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'"-> [2012-01-04T23:21:59.12345Z] length = 26 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'", Utility.UTC_ZONE); - } - else if (dateString.length() == 25) { - // "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"-> [2012-01-04T23:21:59.1234Z] length = 25 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'", Utility.UTC_ZONE); - } - else if (dateString.length() == 24) { - // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"-> [2012-01-04T23:21:59.123Z] length = 24 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Utility.UTC_ZONE); - } - else if (dateString.length() == 23) { - // "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"-> [2012-01-04T23:21:59.12Z] length = 23 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.SS'Z'", Utility.UTC_ZONE); - } - else if (dateString.length() == 22) { - // "yyyy-MM-dd'T'HH:mm:ss.S'Z'"-> [2012-01-04T23:21:59.1Z] length = 22 - return Utility.parseDateFromString(dateString, "yyyy-MM-dd'T'HH:mm:ss.S'Z'", Utility.UTC_ZONE); - } - else { + String pattern = MAX_PRECISION_PATTERN; + switch(dateString.length()) { + case 28: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"-> [2012-01-04T23:21:59.1234567Z] length = 28 + case 27: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"-> [2012-01-04T23:21:59.123456Z] length = 27 + case 26: // "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'"-> [2012-01-04T23:21:59.12345Z] length = 26 + case 25: // "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"-> [2012-01-04T23:21:59.1234Z] length = 25 + case 24: // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"-> [2012-01-04T23:21:59.123Z] length = 24 + dateString = dateString.substring(0, MAX_PRECISION_DATESTRING_LENGTH); + break; + case 23: // "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"-> [2012-01-04T23:21:59.12Z] length = 23 + // SS is assumed to be milliseconds, so a trailing 0 is necessary + dateString = dateString.replace("Z", "0"); + break; + case 22: // "yyyy-MM-dd'T'HH:mm:ss.S'Z'"-> [2012-01-04T23:21:59.1Z] length = 22 + // S is assumed to be milliseconds, so trailing 0's are necessary + dateString = dateString.replace("Z", "00"); + break; + case 20: // "yyyy-MM-dd'T'HH:mm:ss'Z'"-> [2012-01-04T23:21:59Z] length = 20 + pattern = Utility.ISO8601_PATTERN; + break; + case 17: // "yyyy-MM-dd'T'HH:mm'Z'"-> [2012-01-04T23:21Z] length = 17 + pattern = Utility.ISO8601_PATTERN_NO_SECONDS; + break; + default: throw new IllegalArgumentException(String.format(SR.INVALID_DATE_STRING, dateString)); - } + } + + final DateFormat format = new SimpleDateFormat(pattern, Utility.LOCALE_US); + format.setTimeZone(UTC_ZONE); + try { + return format.parse(dateString); } catch (final ParseException e) { throw new IllegalArgumentException(String.format(SR.INVALID_DATE_STRING, dateString), e); } } + + /** + * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it + * with up to millisecond precision. Use {@link #parseDate(String)} instead unless + * dateBackwardCompatibility is needed. + *

+ * See here for more details. + * + * @param dateString + * the String to be interpreted as a Date + * @param dateBackwardCompatibility + * true to correct Date values that may have been written + * using versions of this library prior to 0.4.0; otherwise, false + * + * @return the corresponding Date object + */ + public static Date parseDate(String dateString, boolean dateBackwardCompatibility) { + if (!dateBackwardCompatibility) { + return parseDate(dateString); + } + + final int beginMilliIndex = 20; // Length of "yyyy-MM-ddTHH:mm:ss." + final int endTenthMilliIndex = 24; // Length of "yyyy-MM-ddTHH:mm:ss.SSSS" + + // Check whether the millisecond and tenth of a millisecond digits are all 0. + if (dateString.length() > endTenthMilliIndex && + "0000".equals(dateString.substring(beginMilliIndex, endTenthMilliIndex))) { + // Remove the millisecond and tenth of a millisecond digits. + // Treat the final three digits (ticks) as milliseconds. + dateString = dateString.substring(0, beginMilliIndex) + dateString.substring(endTenthMilliIndex); + } + + return parseDate(dateString); + } /** * Determines which location can the listing command target by looking at the 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 611b1c7..2297c38 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 @@ -2130,11 +2130,6 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op accessCondition, file.properties); } - @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 { 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 256ac7a..faddcad 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 @@ -166,7 +166,7 @@ public Iterable listShares(final String prefix, final ShareListi */ @DoesServiceRequest public ResultSegment listSharesSegmented() throws StorageException { - return this.listSharesSegmented(null, ShareListingDetails.NONE, 0, null /* continuationToken */, + return this.listSharesSegmented(null, ShareListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -186,7 +186,7 @@ public ResultSegment listSharesSegmented() throws StorageExcepti */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix) throws StorageException { - return this.listSharesWithPrefixSegmented(prefix, ShareListingDetails.NONE, 0, null /* continuationToken */, + return this.listSharesWithPrefixSegmented(prefix, ShareListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -199,28 +199,29 @@ public ResultSegment listSharesSegmented(final String prefix) th * @param detailsIncluded * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token returned + * by a previous listing operation. * @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}). + * 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. + * 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 ResultSegment} object that contains a segment of the enumerable collection of - * {@link CloudFileShare} objects that represent the shares for this client. + * @return A {@link ResultSegment} object that contains a segment of the enumerable collection + * of {@link CloudFileShare} objects that represent the shares for this client. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix, - final ShareListingDetails detailsIncluded, final int maxResults, + final ShareListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, final FileRequestOptions options, final OperationContext opContext) throws StorageException { return this.listSharesWithPrefixSegmented(prefix, detailsIncluded, maxResults, continuationToken, options, @@ -259,7 +260,7 @@ private Iterable listSharesWithPrefix(final String prefix, SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable(this.listSharesWithPrefixSegmentedImpl( - prefix, detailsIncluded, -1, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), + prefix, detailsIncluded, null, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), opContext); } @@ -272,27 +273,28 @@ private Iterable listSharesWithPrefix(final String prefix, * @param detailsIncluded * A {@link FileListingDetails} value that indicates whether share metadata will be returned. * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token returned + * by a previous listing operation. * @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}). + * 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. + * 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 ResultSegment} object that contains a segment of the enumerable collection of - * {@link CloudFileShare} objects that represent the shares for this client. + * @return A {@link ResultSegment} object that contains a segment of the enumerable collection + * of {@link CloudFileShare} objects that represent the shares for this client. * * @throws StorageException * If a storage service error occurred. */ private ResultSegment listSharesWithPrefixSegmented(final String prefix, - final ShareListingDetails detailsIncluded, final int maxResults, + final ShareListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -313,7 +315,7 @@ private ResultSegment listSharesWithPrefixSegmented(final String } private StorageRequest> listSharesWithPrefixSegmentedImpl( - final String prefix, final ShareListingDetails detailsIncluded, final int maxResults, + final String prefix, final ShareListingDetails detailsIncluded, final Integer maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.SHARE); @@ -369,7 +371,7 @@ public ResultSegment postProcessResponse(HttpURLConnection conne } final ResultSegment resSegment = new ResultSegment( - response.getResults(), maxResults, newToken); + response.getResults(), response.getMaxResults(), newToken); // Important for listShares because this is required by the lazy iterator between executions. segmentedRequest.setToken(resSegment.getContinuationToken()); 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 704b202..c11afcf 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 @@ -644,7 +644,7 @@ public Iterable listFilesAndDirectories(FileRequestOptions options SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable( - this.listFilesAndDirectoriesSegmentedImpl(-1, options, segmentedRequest), this.fileServiceClient, this, + this.listFilesAndDirectoriesSegmentedImpl(null, options, segmentedRequest), this.fileServiceClient, this, options.getRetryPolicyFactory(), opContext); } @@ -657,8 +657,8 @@ public Iterable listFilesAndDirectories(FileRequestOptions options */ @DoesServiceRequest public ResultSegment listFilesAndDirectoriesSegmented() throws StorageException { - return this - .listFilesAndDirectoriesSegmented(0, null /* continuationToken */, null /* options */, null /* opContext */); + return this.listFilesAndDirectoriesSegmented( + null, null /* continuationToken */, null /* options */, null /* opContext */); } /** @@ -666,18 +666,19 @@ public ResultSegment listFilesAndDirectoriesSegmented() throws Sto * specified listing details options, request options, and operation context. * * @param maxResults - * The maximum number of results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken - * A {@link ResultContinuation} object that represents a continuation token returned by a previous - * listing operation. + * A {@link ResultContinuation} object that represents a continuation token + * returned by a previous listing operation. * @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}). + * 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. + * 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 ResultSegment} object that contains a segment of the enumerable collection of * {@link ListFileItem} objects that represent the files and directories in this directory. @@ -685,7 +686,8 @@ public ResultSegment listFilesAndDirectoriesSegmented() throws Sto * @throws StorageException * If a storage service error occurred. */ - public ResultSegment listFilesAndDirectoriesSegmented(final int maxResults, + @DoesServiceRequest + public ResultSegment listFilesAndDirectoriesSegmented(final Integer maxResults, final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -706,7 +708,7 @@ public ResultSegment listFilesAndDirectoriesSegmented(final int ma } private StorageRequest> listFilesAndDirectoriesSegmentedImpl( - final int maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) { + final Integer maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.FILE); @@ -760,7 +762,7 @@ public ResultSegment postProcessResponse(HttpURLConnection connect } final ResultSegment resSegment = new ResultSegment(response.getResults(), - maxResults, newToken); + response.getMaxResults(), newToken); // Important for listFilesAndDirectories because this is required by the lazy iterator between executions. segmentedRequest.setToken(resSegment.getContinuationToken()); @@ -791,12 +793,12 @@ public CloudFile getFileReference(final String fileName) throws URISyntaxExcepti return new CloudFile(subdirectoryUri, this.fileServiceClient, this.getShare()); } - + /** - * Returns a reference to a {@link CloudFileDirectory} object that represents a subdirectory in this directory. + * Returns a reference to a {@link CloudFileDirectory} object that represents a directory in this directory. * * @param itemName - * A String that represents the name of the subdirectory. + * A String that represents the name of the directory. * * @return A {@link CloudFileDirectory} object that represents a reference to the specified directory. * @@ -804,7 +806,7 @@ public CloudFile getFileReference(final String fileName) throws URISyntaxExcepti * If the resource URI is invalid. * @throws StorageException */ - public CloudFileDirectory getSubDirectoryReference(final String itemName) throws URISyntaxException, + public CloudFileDirectory getDirectoryReference(final String itemName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("itemName", itemName); @@ -812,6 +814,26 @@ public CloudFileDirectory getSubDirectoryReference(final String itemName) throws 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 0.4.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. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileListHandler.java index 9022096..eb3a4f2 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileListHandler.java @@ -123,7 +123,7 @@ public void endElement(String uri, String localName, String qName) throws SAXExc else if (FileConstants.DIRECTORY_ELEMENT.equals(currentNode)) { CloudFileDirectory retDirectory = null; try { - retDirectory = this.directory.getSubDirectoryReference(this.name); + retDirectory = this.directory.getDirectoryReference(this.name); } catch (URISyntaxException e) { throw new SAXException(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 74b2fd8..473ef29 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 @@ -170,7 +170,7 @@ public Iterable listQueues(final String prefix, final QueueListingDe SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest(); return new LazySegmentedIterable(this.listQueuesSegmentedImpl(prefix, - detailsIncluded, -1, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), opContext); + detailsIncluded, null, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), opContext); } /** @@ -187,7 +187,7 @@ public Iterable listQueues(final String prefix, final QueueListingDe */ @DoesServiceRequest public ResultSegment listQueuesSegmented() throws StorageException { - return this.listQueuesSegmented(null, QueueListingDetails.NONE, 0, null, null, null); + return this.listQueuesSegmented(null, QueueListingDetails.NONE, null, null, null, null); } /** @@ -204,7 +204,7 @@ public ResultSegment listQueuesSegmented() throws StorageException { */ @DoesServiceRequest public ResultSegment listQueuesSegmented(final String prefix) throws StorageException { - return this.listQueuesSegmented(prefix, QueueListingDetails.NONE, 0, null, null, null); + return this.listQueuesSegmented(prefix, QueueListingDetails.NONE, null, null, null, null); } /** @@ -219,32 +219,31 @@ public ResultSegment listQueuesSegmented(final String prefix) throws * A {@link QueueListingDetails} value that indicates whether * queue metadata will be returned. * @param maxResults - * The maximum number of queue results to retrieve. + * The maximum number of results to retrieve. If null or greater + * than 5000, the server will return up to 5,000 items. Must be at least 1. * @param continuationToken * A {@link ResultContinuation} object that represents a * continuation token returned by a previous listing operation. * @param options - * A {@link QueueRequestOptions} object that specifies any - * additional options for the request. Specifying null will use the default request options - * from - * the associated service client ( {@link CloudQueue}). + * A {@link QueueRequestOptions} object that specifies any additional options for + * the request. Specifying null will use the default request options + * from the associated service client ( {@link CloudQueue}). * @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 ResultSegment} of {@link CloudQueue} objects that - * contains a segment of the iterable collection of {@link CloudQueue} objects that represent the requested - * queues in - * the storage service. + * @return A {@link ResultSegment} of {@link CloudQueue} objects that contains a segment of + * the iterable collection of {@link CloudQueue} objects that represent the requested + * queues in the storage service. * * @throws StorageException * If a storage service error occurred during the operation. */ @DoesServiceRequest public ResultSegment listQueuesSegmented(final String prefix, - final QueueListingDetails detailsIncluded, final int maxResults, + final QueueListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, QueueRequestOptions options, OperationContext opContext) throws StorageException { @@ -263,7 +262,7 @@ public ResultSegment listQueuesSegmented(final String prefix, } private StorageRequest> listQueuesSegmentedImpl( - final String prefix, final QueueListingDetails detailsIncluded, final int maxResults, + final String prefix, final QueueListingDetails detailsIncluded, final Integer maxResults, final QueueRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.QUEUE); @@ -318,7 +317,7 @@ public ResultSegment postProcessResponse(HttpURLConnection connectio } final ResultSegment resSegment = new ResultSegment(response.getResults(), - maxResults, newToken); + response.getMaxResults(), newToken); // Important for listQueues because this is required by the lazy iterator between executions. segmentedRequest.setToken(resSegment.getContinuationToken()); 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 7601044..36a1fbe 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 @@ -295,6 +295,10 @@ public ResultSegment listTablesSegmented(final String prefix) throws Sto public ResultSegment listTablesSegmented(final String prefix, final Integer maxResults, final ResultContinuation continuationToken, final TableRequestOptions options, final OperationContext opContext) throws StorageException { + if (null != maxResults) { + Utility.assertGreaterThanOrEqual("maxResults", maxResults, 1); + } + return (ResultSegment) this.executeQuerySegmentedImpl( this.generateListTablesQuery(prefix).take(maxResults), this.tableNameResolver, continuationToken, options, opContext); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/EntityProperty.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/EntityProperty.java index 0a0891a..f034889 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/EntityProperty.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/EntityProperty.java @@ -42,6 +42,13 @@ public final class EntityProperty { private Class type; private EdmType edmType = EdmType.NULL; private boolean isNull = false; + + /** + * Flag that specifies 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 0.4.0. + * See here for more details. + */ + private boolean dateBackwardCompatibility = false; /** * Reserved for internal use. Constructs an {@link EntityProperty} instance from a Object value type, @@ -405,7 +412,7 @@ public boolean getIsNull() { public Class getType() { return this.type; } - + /** * Gets the value of this {@link EntityProperty} as a boolean. * @@ -471,7 +478,7 @@ public Date getValueAsDate() { if (this.isNull) { return null; } - return Utility.parseDate(this.value); + return Utility.parseDate(this.value, this.dateBackwardCompatibility); } /** @@ -710,7 +717,7 @@ public synchronized final void setValue(final Date value) { this.isNull = false; } - this.value = Utility.getTimeByZoneAndFormat(value, Utility.UTC_ZONE, Utility.ISO8061_LONG_PATTERN); + this.value = Utility.getJavaISO8601Time(value); } /** @@ -852,4 +859,20 @@ public synchronized final void setValue(final UUID value) { this.value = value.toString(); } + + /** + * Sets 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 0.4.0. + *

+ * {@link #dateBackwardCompatibility} is by default false, indicating a post 0.4.0 version or + * mixed-platform usage. + *

+ * See here for more details. + * + * @param dateBackwardCompatibility + * true to enable dateBackwardCompatibility; otherwise, false + */ + void setDateBackwardCompatibility(boolean dateBackwardCompatibility) { + this.dateBackwardCompatibility = dateBackwardCompatibility; + } } 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 f0ede58..f934291 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 @@ -57,6 +57,9 @@ final class TableDeserializer { * @param resolver * An {@link EntityResolver} instance to project the entities into instances of type R. Set * to null to return the entities as instances of the class type T. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. * @param opContext * An {@link OperationContext} object used to track the execution of the operation. * @return @@ -157,8 +160,13 @@ static ODataPayload parseQueryResponse(final Input * JsonParser using the specified class type and optionally projects the entity result with the * specified resolver into a {@link TableResult} object. * - * @param parser - * The JsonParser to read the data to parse from. + * @param inStream + * The InputStream to read the data to parse from. + * @param format + * The {@link TablePayloadFormat} to use for parsing. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. * @param httpStatusCode * The HTTP status code returned with the operation response. * @param clazzType @@ -214,6 +222,9 @@ static TableResult parseSingleOpResponse(final InputS * @param resolver * An {@link EntityResolver} instance to project the entity into an instance of type R. Set * to null to return the entity as an instance of the class type T. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. * @param opContext * An {@link OperationContext} object used to track the execution of the operation. * @return @@ -292,6 +303,7 @@ private static TableResult parseJsonEntity(final Json } final EntityProperty newProp = new EntityProperty(val, edmType); + newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(key, newProp); parser.nextToken(); @@ -315,6 +327,7 @@ private static TableResult parseJsonEntity(final Json tempProp = properties.remove(TableConstants.TIMESTAMP); if (tempProp != null) { + tempProp.setDateBackwardCompatibility(false); timestamp = tempProp.getValueAsDate(); if (res.getEtag() == null) { @@ -344,6 +357,7 @@ private static TableResult parseJsonEntity(final Json // try to create a new entity property using the returned type try { final EntityProperty newProp = new EntityProperty(value, edmType); + newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(p.getKey(), newProp); } catch (IllegalArgumentException e) { @@ -362,6 +376,7 @@ else if (clazzType != null) { if (propPair != null) { final EntityProperty newProp = new EntityProperty(p.getValue().getValueAsString(), propPair.type); + newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(p.getKey(), newProp); } } @@ -383,7 +398,7 @@ else if (clazzType != null) { entity.setPartitionKey(partitionKey); entity.setRowKey(rowKey); entity.setTimestamp(timestamp); - + entity.readEntity(res.getProperties(), opContext); res.setResult(entity); 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 90f10eb..2bac9e1 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 @@ -141,8 +141,7 @@ private static void writeJsonEntity(final JsonGenerator generator, final TableEn generator.writeStringField(TableConstants.ROW_KEY, entity.getRowKey()); // Timestamp - generator.writeStringField(TableConstants.TIMESTAMP, Utility.getTimeByZoneAndFormat(entity.getTimestamp(), - Utility.UTC_ZONE, Utility.ISO8061_LONG_PATTERN)); + generator.writeStringField(TableConstants.TIMESTAMP, Utility.getJavaISO8601Time(entity.getTimestamp())); } for (final Entry ent : properties.entrySet()) { 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 f330cdb..8507dd2 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 @@ -260,8 +260,7 @@ public static String generateFilterCondition(String propertyName, String operati */ public static String generateFilterCondition(String propertyName, String operation, final Date value) { return generateFilterCondition(propertyName, operation, - Utility.getTimeByZoneAndFormat(value, Utility.UTC_ZONE, Utility.ISO8061_LONG_PATTERN), - EdmType.DATE_TIME); + Utility.getJavaISO8601Time(value), EdmType.DATE_TIME); } /** 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 98a228c..d7dcb5f 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 @@ -55,11 +55,18 @@ public interface PropertyResolver { private PropertyResolver propertyResolver; /** - * The here for more details. + */ + private boolean dateBackwardCompatibility = false; /** * Creates an instance of the TableRequestOptions @@ -80,6 +87,7 @@ public TableRequestOptions(final TableRequestOptions other) { if (other != null) { this.setTablePayloadFormat(other.getTablePayloadFormat()); this.setPropertyResolver(other.getPropertyResolver()); + this.dateBackwardCompatibility = other.dateBackwardCompatibility; } } @@ -160,6 +168,20 @@ public PropertyResolver getPropertyResolver() { return this.propertyResolver; } + /** + * 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 0.4.0, + * see {@link #setDateBackwardCompatibility(boolean)}. + *

+ * See here for more details. + * + * @return + * true if dateBackwardCompatibility is enabled; otherwise, false + */ + public boolean getDateBackwardCompatibility() { + return this.dateBackwardCompatibility; + } + /** * Sets the {@link TablePayloadFormat} to be used. *

@@ -190,4 +212,22 @@ public void setTablePayloadFormat(TablePayloadFormat payloadFormat) { public void setPropertyResolver(PropertyResolver propertyResolver) { this.propertyResolver = propertyResolver; } + + /** + * Sets 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 0.4.0. + *

+ * {@link #dateBackwardCompatibility} is by default false, indicating a post 0.4.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 + * subsequent requests made via the service client will use that {@link #dateBackwardCompatibility}. + *

+ * See here for more details. + * + * @param dateBackwardCompatibility + * true to enable dateBackwardCompatibility; otherwise, false + */ + public void setDateBackwardCompatibility(boolean dateBackwardCompatibility) { + this.dateBackwardCompatibility = dateBackwardCompatibility; + } } 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 1948e13..536332c 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 @@ -78,12 +78,6 @@ * A Boolean value. * * - * Edm.Byte - * {@link EdmType#BYTE} - * byte, Byte - * An 8-bit integer value. - * - * * Edm.DateTime * {@link EdmType#DATE_TIME} * java.util.Date