From 4abe59b0a73368049c712325e4debbcb04fe5ea4 Mon Sep 17 00:00:00 2001 From: Emily Gerner Date: Tue, 26 May 2015 16:13:38 -0700 Subject: [PATCH] Android Storage Client Library 0.5.1 --- ChangeLog.txt | 8 + README.md | 4 +- .../AndroidManifest.xml | 2 +- .../AndroidManifest.xml | 2 +- .../microsoft/azure/storage/GenericTests.java | 180 ---------- .../storage/MaximumExecutionTimeTests.java | 325 ++++++++++++++++++ .../storage/blob/CloudBlockBlobTests.java | 39 +++ .../azure/storage/runners/CoreTestSuite.java | 2 + .../storage/table/TableOperationTests.java | 17 + .../azure/storage/table/TableQueryTests.java | 61 ++++ microsoft-azure-storage/AndroidManifest.xml | 3 +- microsoft-azure-storage/pom.xml | 2 +- .../microsoft/azure/storage/Constants.java | 7 +- .../storage/blob/BlobRequestOptions.java | 4 +- .../azure/storage/blob/CloudBlob.java | 3 +- .../azure/storage/blob/CloudBlockBlob.java | 9 +- .../azure/storage/blob/CloudPageBlob.java | 3 +- .../azure/storage/core/BaseRequest.java | 2 +- .../microsoft/azure/storage/core/Utility.java | 14 +- .../azure/storage/file/CloudFile.java | 4 +- .../storage/file/FileRequestOptions.java | 25 +- .../storage/queue/QueueRequestOptions.java | 2 +- .../storage/table/QueryTableOperation.java | 2 +- .../storage/table/TableDeserializer.java | 5 +- .../azure/storage/table/TableOperation.java | 21 +- .../azure/storage/table/TableQuery.java | 2 +- .../storage/table/TableRequestOptions.java | 2 +- 27 files changed, 525 insertions(+), 225 deletions(-) create mode 100644 microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 716962b..87dc043 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,11 @@ +2015.05.26 Version 0.5.1 + * Fixed a bug where maximum execution time was ignored for file, queue, and table services. + * Changed the socket timeout to be set to the service side timeout plus 5 minutes when maximum execution time is not set. + * Changed the socket timeout to default to 5 minutes rather than infinite when neither service side timeout or maximum execution time are set. + * Fixed a bug where MD5 was calculated for commitBlockList even though UseTransactionalMD5 was set to false. + * Fixed a bug where selecting fields that did not exist returned an error rather than an EntityProperty with a null value. + * Fixed a bug where table entities with a single quote in their partition or row key could be inserted but not operated on in any other way. + 2015.04.02 Version 0.5.0 * Fixed a bug for all listing API's where next() would sometimes throw an exception if hasNext() had not been called even if there were more elements to iterate on. * Added sequence number to the blob properties. This is populated for page blobs. diff --git a/README.md b/README.md index b6fda37..d6482de 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ First, add mavenCentral to your repositories by adding the following to your gra Then, add a dependency by adding the following to your gradle build file: dependencies { - compile 'com.microsoft.azure.android:azure-storage-android:0.5.0@aar' + compile 'com.microsoft.azure.android:azure-storage-android:0.5.1@aar' } ###Option 4: aar via Maven @@ -55,7 +55,7 @@ To get the binaries of this library as distributed by Microsoft, ready for use w com.microsoft.azure.android azure-storage-android - 0.5.0 + 0.5.1 aar ``` diff --git a/microsoft-azure-storage-samples/AndroidManifest.xml b/microsoft-azure-storage-samples/AndroidManifest.xml index 5f99a50..c09f750 100644 --- a/microsoft-azure-storage-samples/AndroidManifest.xml +++ b/microsoft-azure-storage-samples/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="0.5.1" > + android:versionName="0.5.1" > () { - - @Override - public void eventOccurred(SendingRequestEvent eventArg) { - try { - Thread.sleep(timeInMs); - } - catch (InterruptedException e) { - // do nothing - } - } - }); - } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java new file mode 100644 index 0000000..8ff77f3 --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java @@ -0,0 +1,325 @@ +/** + * Copyright Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.microsoft.azure.storage; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.UUID; + +import junit.framework.TestCase; + +import com.microsoft.azure.storage.blob.BlobOutputStream; +import com.microsoft.azure.storage.blob.BlobRequestOptions; +import com.microsoft.azure.storage.blob.BlobTestHelper; +import com.microsoft.azure.storage.blob.CloudBlobClient; +import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.blob.CloudBlockBlob; +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.file.CloudFileClient; +import com.microsoft.azure.storage.file.CloudFileShare; +import com.microsoft.azure.storage.file.FileRequestOptions; +import com.microsoft.azure.storage.queue.CloudQueue; +import com.microsoft.azure.storage.queue.CloudQueueClient; +import com.microsoft.azure.storage.queue.QueueRequestOptions; +import com.microsoft.azure.storage.table.CloudTable; +import com.microsoft.azure.storage.table.CloudTableClient; +import com.microsoft.azure.storage.table.DynamicTableEntity; +import com.microsoft.azure.storage.table.TableOperation; +import com.microsoft.azure.storage.table.TableRequestOptions; + +public class MaximumExecutionTimeTests extends TestCase { + + public void testBlobMaximumExecutionTime() throws URISyntaxException, StorageException { + OperationContext opContext = new OperationContext(); + setDelay(opContext, 2500); + + // set the maximum execution time + BlobRequestOptions options = new BlobRequestOptions(); + options.setMaximumExecutionTimeInMs(2000); + + // set the location mode to secondary, secondary request should fail + // so set the timeout low to save time failing (or fail with a timeout) + options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY); + options.setTimeoutIntervalInMs(1000); + + CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); + CloudBlobContainer container = blobClient.getContainerReference(generateRandomName("container")); + + try { + // 1. download attributes will fail as the container does not exist + // 2. the executor will attempt to retry as it is accessing secondary + // 3. maximum execution time should prevent the retry from being made + container.downloadAttributes(null, options, opContext); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getMessage()); + } + } + + public void testFileMaximumExecutionTime() throws URISyntaxException, StorageException { + OperationContext opContext = new OperationContext(); + setDelay(opContext, 2500); + + opContext.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + // Set status code to 500 to force a retry + eventArg.getRequestResult().setStatusCode(500); + } + }); + + // set the maximum execution time + FileRequestOptions options = new FileRequestOptions(); + options.setMaximumExecutionTimeInMs(2000); + options.setTimeoutIntervalInMs(1000); + + CloudFileClient fileClient = TestHelper.createCloudFileClient(); + CloudFileShare share = fileClient.getShareReference(generateRandomName("share")); + + try { + // 1. download attributes will fail as the share does not exist + // 2. the executor will attempt to retry as we set the status code to 500 + // 3. maximum execution time should prevent the retry from being made + share.downloadAttributes(null, options, opContext); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getMessage()); + } + } + + public void testQueueMaximumExecutionTime() throws URISyntaxException, StorageException { + OperationContext opContext = new OperationContext(); + setDelay(opContext, 2500); + + // set the maximum execution time + QueueRequestOptions options = new QueueRequestOptions(); + options.setMaximumExecutionTimeInMs(2000); + + // set the location mode to secondary, secondary request should fail + // so set the timeout low to save time failing (or fail with a timeout) + options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY); + options.setTimeoutIntervalInMs(1000); + + CloudQueueClient queueClient = TestHelper.createCloudQueueClient(); + CloudQueue queue = queueClient.getQueueReference(generateRandomName("queue")); + + try { + // 1. download attributes will fail as the queue does not exist + // 2. the executor will attempt to retry as it is accessing secondary + // 3. maximum execution time should prevent the retry from being made + queue.downloadAttributes(options, opContext); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getMessage()); + } + } + + public void testTableMaximumExecutionTime() throws URISyntaxException, StorageException { + OperationContext opContext = new OperationContext(); + setDelay(opContext, 2500); + + opContext.getResponseReceivedEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + // Set status code to 500 to force a retry + eventArg.getRequestResult().setStatusCode(500); + } + }); + + // set the maximum execution time + TableRequestOptions options = new TableRequestOptions(); + options.setMaximumExecutionTimeInMs(2000); + options.setTimeoutIntervalInMs(1000); + + CloudTableClient tableClient = TestHelper.createCloudTableClient(); + CloudTable table = tableClient.getTableReference(generateRandomName("share")); + + try { + // 1. insert entity will fail as the table does not exist + // 2. the executor will attempt to retry as we set the status code to 500 + // 3. maximum execution time should prevent the retry from being made + DynamicTableEntity ent = new DynamicTableEntity("partition", "row"); + TableOperation insert = TableOperation.insert(ent); + table.execute(insert, options, opContext); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getMessage()); + } + } + + public void testMaximumExecutionTimeBlobWrites() throws URISyntaxException, StorageException, IOException { + byte[] buffer = BlobTestHelper.getRandomBuffer(80 * 1024 * 1024); + + // set the maximum execution time + BlobRequestOptions options = new BlobRequestOptions(); + options.setMaximumExecutionTimeInMs(5000); + + CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); + CloudBlobContainer container = blobClient.getContainerReference(generateRandomName("container")); + + String blobName = "testBlob"; + final CloudBlockBlob blockBlobRef = container.getBlockBlobReference(blobName); + blockBlobRef.setStreamWriteSizeInBytes(1 * 1024 * 1024); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); + BlobOutputStream blobOutputStream = null; + + try { + container.createIfNotExists(); + + // make sure max timeout is thrown by Utility.writeToOutputStream() on upload + try { + blockBlobRef.upload(inputStream, buffer.length, null, options, null); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getMessage()); + } + catch (IOException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + assertFalse(blockBlobRef.exists()); + + // make sure max timeout applies on a per service request basis if the user creates the stream + // adds a delay so the first service request should fail + OperationContext opContext = new OperationContext(); + setDelay(opContext, 6000); + blobOutputStream = blockBlobRef.openOutputStream(null, options, opContext); + try { + blobOutputStream.write(inputStream, buffer.length); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + catch (IOException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + finally { + try { + blobOutputStream.close(); + } + catch (IOException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + } + assertFalse(blockBlobRef.exists()); + + // make sure max timeout applies on a per service request basis if the user creates the stream + // adds a delay so the first service request should fail + blobOutputStream = blockBlobRef.openOutputStream(null, options, opContext); + try { + blobOutputStream.write(buffer); + fail("Maximum execution time was reached but request did not fail."); + } + catch (IOException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + finally { + try { + blobOutputStream.close(); + } + catch (IOException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + } + assertFalse(blockBlobRef.exists()); + + // make sure max timeout applies on a per service request basis only if the user creates the stream + // should succeed as even if all requests would exceed the timeout, each one won't + blobOutputStream = blockBlobRef.openOutputStream(null, options, null); + try { + blobOutputStream.write(inputStream, buffer.length); + } + finally { + blobOutputStream.close(); + } + assertTrue(blockBlobRef.exists()); + } + finally { + inputStream.close(); + container.deleteIfExists(); + } + } + + public void testMaximumExecutionTimeBlobByteArray() throws URISyntaxException, StorageException, IOException { + int length = 10 * 1024 * 1024; + byte[] uploadBuffer = BlobTestHelper.getRandomBuffer(length); + byte[] downloadBuffer = new byte[length]; + + // set a delay in sending request + OperationContext opContext = new OperationContext(); + setDelay(opContext, 2500); + + // set the maximum execution time + BlobRequestOptions options = new BlobRequestOptions(); + options.setMaximumExecutionTimeInMs(2000); + + CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); + CloudBlobContainer container = blobClient.getContainerReference(generateRandomName("container")); + + String blobName = "testBlob"; + final CloudBlockBlob blockBlobRef = container.getBlockBlobReference(blobName); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(uploadBuffer); + + try { + container.createIfNotExists(); + + blockBlobRef.upload(inputStream, length); + assertTrue(blockBlobRef.exists()); + + try { + blockBlobRef.downloadToByteArray(downloadBuffer, 0, null, options, opContext); + fail("Maximum execution time was reached but request did not fail."); + } + catch (StorageException e) { + assertEquals(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION, e.getCause().getMessage()); + } + } + finally { + inputStream.close(); + container.deleteIfExists(); + } + } + + private static String generateRandomName(String prefix) { + String name = prefix + UUID.randomUUID().toString(); + return name.replace("-", ""); + } + + private void setDelay(final OperationContext ctx, final int timeInMs) { + + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + try { + Thread.sleep(timeInMs); + } + catch (InterruptedException e) { + // do nothing + } + } + }); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java index bf180fa..13c1a36 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 @@ -450,6 +450,45 @@ public void testBlobDownloadRangeValidationTest() throws StorageException, URISy blockBlobRef.downloadBlockList(); assertEquals(length, blockBlobRef.getProperties().getLength()); } + + public void testCommitBlockListContentMd5() throws URISyntaxException, StorageException, IOException { + int length = 1024; + byte[] buffer = BlobTestHelper.getRandomBuffer(length); + Map blocks = BlobTestHelper.getBlockEntryList(3); + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("blob1"); + + CloudBlockBlob blob = this.container.getBlockBlobReference(blobName); + for (BlockEntry block : blocks.values()) { + blob.uploadBlock(block.getId(), new ByteArrayInputStream(buffer), length); + } + + OperationContext ctx = new OperationContext(); + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection conn = (HttpURLConnection)eventArg.getConnectionObject(); + assertNull(conn.getRequestProperty("Content-MD5")); + } + }); + + blob.commitBlockList(blocks.values(), null, null, ctx); + + BlobRequestOptions opt = new BlobRequestOptions(); + opt.setUseTransactionalContentMD5(true); + + ctx = new OperationContext(); + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection conn = (HttpURLConnection)eventArg.getConnectionObject(); + assertNotNull(conn.getRequestProperty("Content-MD5")); + } + }); + + blob.commitBlockList(blocks.values(), null, opt, ctx); + } public void testDownloadBlockList() throws URISyntaxException, StorageException, IOException { int length = 1024; diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/CoreTestSuite.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/CoreTestSuite.java index 569cb2e..093ef78 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/CoreTestSuite.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/CoreTestSuite.java @@ -19,6 +19,7 @@ import com.microsoft.azure.storage.EventFiringTests; import com.microsoft.azure.storage.GenericTests; +import com.microsoft.azure.storage.MaximumExecutionTimeTests; import com.microsoft.azure.storage.SecondaryTests; import com.microsoft.azure.storage.ServicePropertiesTests; import com.microsoft.azure.storage.StorageAccountTests; @@ -29,6 +30,7 @@ public static Test suite() { TestSuite suite = new TestSuite("CoreTestSuite"); suite.addTestSuite(EventFiringTests.class); suite.addTestSuite(GenericTests.class); + suite.addTestSuite(MaximumExecutionTimeTests.class); suite.addTestSuite(SecondaryTests.class); suite.addTestSuite(ServicePropertiesTests.class); suite.addTestSuite(StorageAccountTests.class); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableOperationTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableOperationTests.java index df9fb1f..59879c1 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableOperationTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableOperationTests.java @@ -76,6 +76,23 @@ public void testRetrieveWithNullResolver() { String.format(SR.ARGUMENT_NULL_OR_EMPTY, SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER)); } } + + public void testEntityWithSingleQuote() throws StorageException { + TableRequestOptions options = new TableRequestOptions(); + options.setTablePayloadFormat(TablePayloadFormat.Json); + + EmptyClass ref = new EmptyClass(); + ref.setPartitionKey("partition'key"); + ref.setRowKey("row'key"); + + this.table.execute(TableOperation.insert(ref), options, null); + this.table.execute(TableOperation.merge(ref), options, null); + this.table.execute(TableOperation.insertOrReplace(ref), options, null); + this.table.execute(TableOperation.insertOrMerge(ref), options, null); + this.table.execute(TableOperation.replace(ref), options, null); + this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), EmptyClass.class), options, null); + this.table.execute(TableOperation.delete(ref), options, null); + } public void testInsertEntityWithoutPartitionKeyRowKey() { 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 3ad9faa..a0482bd 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 @@ -33,6 +33,7 @@ import com.microsoft.azure.storage.table.TableQuery.QueryComparisons; import com.microsoft.azure.storage.table.TableTestHelper.Class1; import com.microsoft.azure.storage.table.TableTestHelper.ComplexEntity; +import com.microsoft.azure.storage.table.TableTestHelper.EmptyClass; /** * Table Query Tests @@ -89,6 +90,66 @@ public void testQueryWithInvalidTakeCount() { assertEquals(ex.getMessage(), "Take count must be positive and greater than 0."); } } + + public void testTableWithSelectOnMissingFields() throws StorageException { + TableRequestOptions options = new TableRequestOptions(); + + options.setTablePayloadFormat(TablePayloadFormat.Json); + testTableWithSelectOnMissingFields(options); + + options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); + testTableWithSelectOnMissingFields(options); + } + + private void testTableWithSelectOnMissingFields(TableRequestOptions options) throws StorageException { + TableQuery projectionQuery = TableQuery.from(DynamicTableEntity.class).where( + "(PartitionKey eq 'javatables_batch_0') and (RowKey eq '000000')"); + + // A exists, F does not + projectionQuery.select(new String[]{"A", "F"}); + + ResultSegment seg = table.executeSegmented(projectionQuery, null, options, null); + assertEquals(1, seg.getResults().size()); + + DynamicTableEntity ent = seg.getResults().get(0); + assertEquals("foo_A", ent.getProperties().get("A").getValueAsString()); + assertEquals(null, ent.getProperties().get("F").getValueAsString()); + assertEquals(EdmType.STRING, ent.getProperties().get("F").getEdmType()); + } + + public void testTableQueryWithSpecialChars() throws StorageException, URISyntaxException { + CloudTable table = TableTestHelper.getRandomTableReference(); + + try { + table.createIfNotExists(); + + testTableQueryWithSpecialChars('\'', table); + testTableQueryWithSpecialChars('=', table); + testTableQueryWithSpecialChars('_', table); + testTableQueryWithSpecialChars(' ', table); + testTableQueryWithSpecialChars('界', table); + } + finally { + table.deleteIfExists(); + } + } + + private void testTableQueryWithSpecialChars(char charToTest, CloudTable table) + throws StorageException, URISyntaxException { + String partitionKey = "partition" + charToTest + "key"; + String rowKey = "row" + charToTest + "key"; + + EmptyClass ref = new EmptyClass(); + ref.setPartitionKey(partitionKey); + ref.setRowKey(rowKey); + + table.execute(TableOperation.insert(ref)); + String condition = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, partitionKey); + ResultSegment seg = table.executeSegmented(TableQuery.from(EmptyClass.class).where(condition), null); + + assertEquals(1, seg.getLength()); + assertEquals(partitionKey, seg.getResults().get(0).getPartitionKey()); + } public void testTableInvalidQuery() throws StorageException { TableRequestOptions options = new TableRequestOptions(); diff --git a/microsoft-azure-storage/AndroidManifest.xml b/microsoft-azure-storage/AndroidManifest.xml index 6223189..d09949e 100644 --- a/microsoft-azure-storage/AndroidManifest.xml +++ b/microsoft-azure-storage/AndroidManifest.xml @@ -11,8 +11,7 @@ - diff --git a/microsoft-azure-storage/pom.xml b/microsoft-azure-storage/pom.xml index cc7f18d..825e54b 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.5.0 + 0.5.1 aar Microsoft Azure Storage Android Client SDK 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 7d82739..a23314b 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -535,7 +535,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "0.5.0"; + public static final String USER_AGENT_VERSION = "0.5.1"; /** * The default type for content-type and accept @@ -774,6 +774,11 @@ public static class QueryConstants { */ public static final String COPY_STATUS_ELEMENT = "CopyStatus"; + /** + * Default read timeout. 5 min * 60 seconds * 1000 ms + */ + public static final int DEFAULT_READ_TIMEOUT = 5 * 60 * 1000; + /** * XML element for delimiters. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java index ff18c31..61205a9 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequestOptions.java @@ -119,11 +119,11 @@ protected static final BlobRequestOptions applyDefaults(final BlobRequestOptions final CloudBlobClient client, final boolean setStartTime) { BlobRequestOptions modifiedOptions = new BlobRequestOptions(options); BlobRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), setStartTime); - return BlobRequestOptions.applyDefaultsInternal(modifiedOptions, blobType, client, setStartTime); + return BlobRequestOptions.applyDefaultsInternal(modifiedOptions, blobType, client); } private static final BlobRequestOptions applyDefaultsInternal(final BlobRequestOptions modifiedOptions, - final BlobType blobtype, final CloudBlobClient client, final boolean setStartTime) { + final BlobType blobtype, final CloudBlobClient client) { Utility.assertNotNull("modifiedOptions", modifiedOptions); RequestOptions.applyBaseDefaultsInternal(modifiedOptions); if (modifiedOptions.getConcurrentRequestCount() == 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 072bd31..45977dc 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 @@ -2369,7 +2369,8 @@ public final BlobInputStream openInputStream(final AccessCondition accessConditi assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); + options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient, + false /* setStartTime */); return new BlobInputStream(this, accessCondition, options, opContext); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java index 4ae81b8..b2c9dc4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java @@ -257,7 +257,7 @@ private StorageRequest commitBlockListImpl(fin // This also marks the stream. Therefore no need to mark it in buildRequest. final StreamMd5AndLength descriptor = Utility.analyzeStream(blockListInputStream, -1L, -1L, - true /* rewindSourceStream */, true /* calculateMD5 */); + true /* rewindSourceStream */, options.getUseTransactionalContentMD5() /* calculateMD5 */); final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @@ -276,7 +276,9 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) { BlobRequest.addMetadata(connection, blob.metadata, context); - connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, descriptor.getMd5()); + if (options.getUseTransactionalContentMD5()) { + connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, descriptor.getMd5()); + } } @Override @@ -476,7 +478,8 @@ public BlobOutputStream openOutputStream(final AccessCondition accessCondition, assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient, false); + options = BlobRequestOptions.applyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient, + false /* setStartTime */); return new BlobOutputStream(this, accessCondition, options, opContext); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java index 99a5ef2..5483c12 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java @@ -574,7 +574,8 @@ private BlobOutputStream openOutputStreamInternal(Long length, AccessCondition a assertNoWriteOperationForSnapshot(); - options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient, false); + options = BlobRequestOptions.applyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient, + false /* setStartTime */); if (options.getStoreBlobContentMD5()) { throw new IllegalArgumentException(SR.BLOB_MD5_NOT_SUPPORTED_FOR_PAGE_BLOBS); 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 536c53b..1a45ad1 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 @@ -175,7 +175,7 @@ public static HttpURLConnection createURLConnection(final URI uri, final Request } // Note: ReadTimeout must be explicitly set - retConnection.setReadTimeout(Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs())); + retConnection.setReadTimeout(Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs(), options.getTimeoutIntervalInMs())); // Note : by default sends Accept behavior as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, Constants.HeaderConstants.XML_TYPE); 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 bc4dd0f..7649d6d 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 @@ -375,15 +375,17 @@ public static boolean validateMaxExecutionTimeout(Long operationExpiryTimeInMs, } /** - * Returns a value representing the remaining time before the operation expires. 0 represents an infinite timeout. + * Returns a value representing the remaining time before the operation expires. * * @param operationExpiryTimeInMs * the time the request expires - * @return the remaining time before the operation expires, or 0 to represent an infinite timeout + * @param timeoutIntervalInMs + * the server side timeout interval + * @return the remaining time before the operation expires * @throws StorageException * wraps a TimeoutException if there is no more time remaining */ - public static int getRemainingTimeout(Long operationExpiryTimeInMs) throws StorageException { + public static int getRemainingTimeout(Long operationExpiryTimeInMs, Integer timeoutIntervalInMs) throws StorageException { if (operationExpiryTimeInMs != null) { long remainingTime = operationExpiryTimeInMs - new Date().getTime(); if (remainingTime > Integer.MAX_VALUE) { @@ -400,9 +402,11 @@ else if (remainingTime > 0) { throw translatedException; } } + else if (timeoutIntervalInMs != null) { + return timeoutIntervalInMs + Constants.DEFAULT_READ_TIMEOUT; + } else { - // represents an infinite timeout - return 0; + return Constants.DEFAULT_READ_TIMEOUT; } } 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 89125b6..a56eb79 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 @@ -1526,7 +1526,7 @@ public final FileInputStream openRead(final AccessCondition accessCondition, Fil opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.applyDefaults(options, this.fileServiceClient, false /* setStartTime */); return new FileInputStream(this, accessCondition, options, opContext); } @@ -1647,7 +1647,7 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a if (opContext == null) { opContext = new OperationContext(); } - options = FileRequestOptions.applyDefaults(options, this.fileServiceClient); + options = FileRequestOptions.applyDefaults(options, this.fileServiceClient, false /* setStartTime */); if (length != null) { if (options.getStoreFileContentMD5()) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java index 8ed491e..b1baa80 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequestOptions.java @@ -91,8 +91,27 @@ public FileRequestOptions(final FileRequestOptions other) { */ protected static final FileRequestOptions applyDefaults(final FileRequestOptions options, final CloudFileClient client) { + return FileRequestOptions.applyDefaults(options, client, true); + } + + /** + * Uses the concurrent request count from the specified client if null, sets a default value for + * everything else, and sets defaults as defined in the parent class. + * + * @param options + * The input options to copy from when applying defaults + * @param client + * A {@link CloudFileClient} object that represents the service client used to set the default timeout + * interval and retry policy, if they are null. Additionally, if the + * {@link #concurrentRequestCount} field's value is null, it will be set to the value specified by the + * cloud blob client's {@link CloudFileClient#getConcurrentRequestCount} method. + * @param setStartTime + * whether to initialize the startTimeInMs field, or not + */ + protected static final FileRequestOptions applyDefaults(final FileRequestOptions options, + final CloudFileClient client, final boolean setStartTime) { FileRequestOptions modifiedOptions = new FileRequestOptions(options); - FileRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions()); + FileRequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), setStartTime); return FileRequestOptions.applyDefaultsInternal(modifiedOptions, client); } @@ -123,8 +142,8 @@ private static final FileRequestOptions applyDefaultsInternal(final FileRequestO * Populates any null fields in the first requestOptions object with values from the second requestOptions object. */ private static final FileRequestOptions populateRequestOptions(FileRequestOptions modifiedOptions, - final FileRequestOptions clientOptions) { - RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, false); + final FileRequestOptions clientOptions, boolean setStartTime) { + RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, setStartTime); if (modifiedOptions.getConcurrentRequestCount() == null) { modifiedOptions.setConcurrentRequestCount(clientOptions.getConcurrentRequestCount()); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java index 8abdcda..6545664 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequestOptions.java @@ -53,7 +53,7 @@ public QueueRequestOptions(final QueueRequestOptions other) { */ protected static final QueueRequestOptions applyDefaults(QueueRequestOptions options, final CloudQueueClient client) { QueueRequestOptions modifiedOptions = new QueueRequestOptions(options); - RequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), false); + RequestOptions.populateRequestOptions(modifiedOptions, client.getDefaultRequestOptions(), true /* setStartTime */); return QueueRequestOptions.applyDefaultsInternal(modifiedOptions, client); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/QueryTableOperation.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/QueryTableOperation.java index 885f75f..3e3cef1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/QueryTableOperation.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/QueryTableOperation.java @@ -202,7 +202,7 @@ public HttpURLConnection buildRequest(CloudTableClient client, QueryTableOperati return TableRequest .query(client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()), options, null/* Query Builder */, context, tableName, - generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), null/* Continuation Token */); + generateRequestIdentity(isTableEntry, operation.getPartitionKey()), null/* Continuation Token */); } @Override 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 f934291..c9632b0 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 @@ -415,10 +415,7 @@ private static String getETagFromTimestamp(String timestampString) throws Unsupp private static EdmType evaluateEdmType(JsonToken token, String value) { EdmType edmType = null; - if (token == JsonToken.VALUE_NULL) { - edmType = EdmType.NULL; - } - else if (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE) { + if (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE) { edmType = EdmType.BOOLEAN; } else if (token == JsonToken.VALUE_NUMBER_FLOAT) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java index 12982d3..754b412 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableOperation.java @@ -307,7 +307,7 @@ private StorageRequest deleteImpl public HttpURLConnection buildRequest(CloudTableClient client, TableOperation operation, OperationContext context) throws Exception { return TableRequest.delete(client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()), - options, null, context, tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), + options, null, context, tableName, generateRequestIdentity(isTableEntry, tableIdentity), operation.getEntity().getEtag()); } @@ -406,7 +406,7 @@ public HttpURLConnection buildRequest(CloudTableClient client, TableOperation op this.setLength((long) entityBytes.length); return TableRequest.insert( client.getTransformedEndPoint(opContext).getUri(this.getCurrentLocation()), options, null, - opContext, tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), + opContext, tableName, generateRequestIdentity(isTableEntry, tableIdentity), operation.opType != TableOperationType.INSERT ? operation.getEntity().getEtag() : null, operation.getEchoContent(), operation.opType.getUpdateType()); } @@ -552,7 +552,7 @@ public HttpURLConnection buildRequest(CloudTableClient client, TableOperation op this.setLength((long) entityBytes.length); return TableRequest.merge(client.getTransformedEndPoint(opContext) .getUri(this.getCurrentLocation()), options, null, opContext, tableName, - generateRequestIdentity(false, null, false), operation.getEntity().getEtag()); + generateRequestIdentity(false, null), operation.getEntity().getEtag()); } @Override @@ -650,7 +650,7 @@ public HttpURLConnection buildRequest(CloudTableClient client, TableOperation op this.setLength((long) entityBytes.length); return TableRequest.update( client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()), options, null, - context, tableName, generateRequestIdentity(false, null, false), operation.getEntity() + context, tableName, generateRequestIdentity(false, null), operation.getEntity() .getEtag()); } @@ -760,17 +760,15 @@ else if (this.getOperationType() == TableOperationType.RETRIEVE) { * @param entryName * The entry name to use as the request identity if the isSingleIndexEntry parameter is * true. - * @param encodeKeys - * Pass true to url encode the partition & row keys * @return * A String which represents the formatted request identity string. * @throws StorageException * If a storage service error occurred. */ - protected String generateRequestIdentity(boolean isSingleIndexEntry, final String entryName, boolean encodeKeys) + protected String generateRequestIdentity(boolean isSingleIndexEntry, final String entryName) throws StorageException { if (isSingleIndexEntry) { - return String.format("'%s'", entryName); + return String.format("'%s'", entryName.replace("'", "''")); } if (this.opType == TableOperationType.INSERT) { @@ -790,8 +788,9 @@ protected String generateRequestIdentity(boolean isSingleIndexEntry, final Strin rk = this.getEntity().getRowKey(); } - return String.format("%s='%s',%s='%s'", TableConstants.PARTITION_KEY, encodeKeys ? Utility.safeEncode(pk) - : pk, TableConstants.ROW_KEY, encodeKeys ? Utility.safeEncode(rk) : rk); + return String.format("%s='%s',%s='%s'", + TableConstants.PARTITION_KEY, pk.replace("'", "''"), + TableConstants.ROW_KEY, rk.replace("'", "''")); } } @@ -807,7 +806,7 @@ protected String generateRequestIdentity(boolean isSingleIndexEntry, final Strin * @throws StorageException */ protected String generateRequestIdentityWithTable(final String tableName) throws StorageException { - return String.format("%s(%s)", tableName, generateRequestIdentity(false, null, false)); + return String.format("%s(%s)", tableName, generateRequestIdentity(false, null)); } /** 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 8507dd2..ee09f20 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 @@ -393,7 +393,7 @@ else if (edmType == EdmType.BINARY) { valueOperand = String.format("X'%s'", value); } else { - valueOperand = String.format("'%s'", value); + valueOperand = String.format("'%s'", value.replace("'", "''")); } return String.format("%s %s %s", propertyName, operation, valueOperand); 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 d7dcb5f..69df64b 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 @@ -133,7 +133,7 @@ private static final TableRequestOptions applyDefaultsInternal(final TableReques */ private static final RequestOptions populateRequestOptions(TableRequestOptions modifiedOptions, final TableRequestOptions clientOptions) { - RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, false); + RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, true /* setStartTime */); if (modifiedOptions.getTablePayloadFormat() == null) { modifiedOptions.setTablePayloadFormat(clientOptions.getTablePayloadFormat()); }