From f37855a881da1da37c4212c79e830543a61ade85 Mon Sep 17 00:00:00 2001 From: Emily Gerner Date: Thu, 2 Apr 2015 16:40:22 -0700 Subject: [PATCH] Release 0.5.0 --- .gitignore | 2 +- ChangeLog.txt | 11 + README.md | 4 +- .../AndroidManifest.xml | 2 +- .../AndroidManifest.xml | 2 +- .../azure/storage/ServicePropertiesTests.java | 20 -- .../azure/storage/StorageAccountTests.java | 6 +- .../azure/storage/blob/BlobTestHelper.java | 1 + .../storage/blob/CloudBlobContainerTests.java | 35 +- .../storage/blob/CloudBlobDirectoryTests.java | 3 +- .../storage/blob/CloudBlockBlobTests.java | 35 ++ .../storage/blob/CloudPageBlobTests.java | 11 + .../azure/storage/queue/CloudQueueTests.java | 13 +- .../azure/storage/runners/TableTestSuite.java | 2 + .../azure/storage/table/TableDateTests.java | 314 +++++++++++++++++ .../azure/storage/table/TableQueryTests.java | 274 --------------- microsoft-azure-storage/AndroidManifest.xml | 2 +- microsoft-azure-storage/pom.xml | 2 +- .../azure/storage/AccessCondition.java | 318 +++++++++++++----- .../microsoft/azure/storage/Constants.java | 24 +- .../azure/storage/ServiceClient.java | 12 +- .../azure/storage/StorageException.java | 245 +++++--------- .../azure/storage/blob/BlobOutputStream.java | 20 +- .../azure/storage/blob/BlobProperties.java | 28 +- .../azure/storage/blob/BlobRequest.java | 37 +- .../azure/storage/blob/BlobResponse.java | 8 +- .../azure/storage/blob/CloudBlob.java | 104 +----- .../storage/blob/CloudBlobContainer.java | 6 +- .../azure/storage/blob/CloudBlockBlob.java | 105 +++++- .../azure/storage/blob/CloudPageBlob.java | 31 +- .../azure/storage/core/BaseRequest.java | 50 ++- .../azure/storage/core/ExecutionEngine.java | 19 +- .../storage/core/LazySegmentedIterator.java | 6 +- .../azure/storage/core/LogConstants.java | 2 +- .../microsoft/azure/storage/core/Logger.java | 2 +- .../{ => core}/StorageErrorHandler.java | 6 +- .../azure/storage/core/StorageRequest.java | 24 +- .../microsoft/azure/storage/core/Utility.java | 133 ++++++++ .../azure/storage/file/CloudFileClient.java | 2 +- .../azure/storage/file/FileOutputStream.java | 21 +- .../azure/storage/file/FileRequest.java | 21 +- .../azure/storage/queue/CloudQueue.java | 30 +- .../azure/storage/queue/QueueRequest.java | 19 +- .../azure/storage/table/CloudTable.java | 24 +- .../azure/storage/table/CloudTableClient.java | 11 + .../storage/table/QueryTableOperation.java | 5 + .../storage/table/TableBatchOperation.java | 10 +- .../azure/storage/table/TableOperation.java | 23 +- .../table/TableStorageErrorDeserializer.java | 26 +- 49 files changed, 1285 insertions(+), 826 deletions(-) create mode 100644 microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableDateTests.java rename microsoft-azure-storage/src/com/microsoft/azure/storage/{ => core}/StorageErrorHandler.java (95%) diff --git a/.gitignore b/.gitignore index ae8787e..cd2f814 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ tmp/**/* *.swp *~.nib local.properties -.settings/** +*/.settings/** .loadpath # External tool builders diff --git a/ChangeLog.txt b/ChangeLog.txt index b86de01..716962b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,14 @@ +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. + * Creating a page blob sets its length property. + * Added support for page blob sequence numbers and sequence number access conditions. + * Fixed a bug in abort copy where the lease access condition was not sent to the service. + * Fixed an issue in startCopyFromBlob where if the URI of the source blob contained certain non-ASCII characters they would not be encoded appropriately. This would result in Authorization failures. + * Fixed a bug in BlobOutputStream and FileOutputStream where flush added data to a request pool rather than immediately committing it to the Azure service. + * Refactored to remove the blob, queue, and file package dependency on table in the error handling code. + * Added additional client-side logging for REST requests, responses, and errors. + 2015.01 Version 0.4.1 * Fixed a bug for Android 5.0 only that caused auth failures on deletes. diff --git a/README.md b/README.md index 590a188..b6fda37 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.4.1@aar' + compile 'com.microsoft.azure.android:azure-storage-android:0.5.0@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.4.1 + 0.5.0 aar ``` diff --git a/microsoft-azure-storage-samples/AndroidManifest.xml b/microsoft-azure-storage-samples/AndroidManifest.xml index 8bc8ff4..5f99a50 100644 --- a/microsoft-azure-storage-samples/AndroidManifest.xml +++ b/microsoft-azure-storage-samples/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="0.5.0" > + android:versionName="0.5.0" > corsRules, ServiceClient client, ServiceProperties props) throws StorageException, InterruptedException { @@ -817,9 +800,6 @@ private void testCorsRules(List corsRules, ServiceClient client, Servi /** * Checks two ServiceProperties for equality - * - * @param propsA - * @param propsB */ private static void assertServicePropertiesAreEqual(ServiceProperties propsA, ServiceProperties propsB) { if (propsA.getLogging() != null && propsB.getLogging() != null) { 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 871eac8..6b98385 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 @@ -68,17 +68,15 @@ public void testStorageCredentialsSharedKey() throws URISyntaxException, Storage } public void testStorageCredentialsSAS() throws URISyntaxException, StorageException { - String token = "?sp=abcde&api-version=2014-02-14&sig=1"; - + String token = "?sig=1&api-version=2014-02-14&sp=abcde"; StorageCredentialsSharedAccessSignature cred = new StorageCredentialsSharedAccessSignature(token); - assertNull(cred.getAccountName()); URI testUri = new URI("http://test/abc"); assertEquals(testUri + token, cred.transformUri(testUri).toString()); testUri = new URI("http://test/abc?query=a&query2=b"); - String expectedUri = "http://test/abc?api-version=2014-02-14&sp=abcde&query=a&query2=b&sig=1"; + String expectedUri = "http://test/abc?sig=1&api-version=2014-02-14&query=a&sp=abcde&query2=b"; assertEquals(expectedUri, cred.transformUri(testUri).toString()); } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java index e6dda99..f9eb54f 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java @@ -315,6 +315,7 @@ public static void assertAreEqual(BlobProperties prop1, BlobProperties prop2) { Assert.assertEquals(prop1.getEtag(), prop2.getEtag()); Assert.assertEquals(prop1.getLastModified(), prop2.getLastModified()); Assert.assertEquals(prop1.getLength(), prop2.getLength()); + Assert.assertEquals(prop1.getPageBlobSequenceNumber(), prop2.getPageBlobSequenceNumber()); } } 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 e56291d..7573916 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 @@ -22,13 +22,13 @@ import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.TimeZone; import java.util.UUID; - import junit.framework.Assert; import junit.framework.TestCase; - +import android.annotation.SuppressLint; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.NameValidator; import com.microsoft.azure.storage.OperationContext; @@ -433,6 +433,34 @@ public void testCloudBlobContainerListBlobs() throws StorageException, IOExcepti assertTrue(blobNames.size() == 0); } + /** + * List the blobs in a container with next(). This tests for the item in the changelog: "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." + * + * @throws URISyntaxException + * @throws StorageException + * @throws IOException + */ + public void testCloudBlobContainerListBlobsNext() throws StorageException, IOException, URISyntaxException { + this.container.create(); + + int numBlobs = 10; + List blobNames = BlobTestHelper.uploadNewBlobs(this.container, BlobType.PAGE_BLOB, 10, 512, null); + assertEquals(numBlobs, blobNames.size()); + + // hasNext first + Iterator iter = this.container.listBlobs().iterator(); + iter.hasNext(); + iter.next(); + iter.next(); + + // next without hasNext + iter = this.container.listBlobs().iterator(); + iter.next(); + iter.next(); + } + /** * Try to list the blobs in a container to ensure maxResults validation is working. * @@ -590,7 +618,8 @@ private static void assertPermissionsEqual(BlobContainerPermissions expected, Bl * @throws StorageException * @throws URISyntaxException */ - private static void assertCreatedAndListedBlobsEquivalent(CloudBlockBlob createdBlob, CloudBlockBlob listedBlob, + @SuppressLint("UseValueOf") + private static void assertCreatedAndListedBlobsEquivalent(CloudBlockBlob createdBlob, CloudBlockBlob listedBlob, int length) throws StorageException, URISyntaxException{ assertEquals(createdBlob.getContainer().getName(), listedBlob.getContainer().getName()); assertEquals(createdBlob.getMetadata(), listedBlob.getMetadata()); 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 9745114..9a5f6cf 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 @@ -362,7 +362,8 @@ private void testFlatListingWithDirectory(String delimiter, CloudBlobContainer c null), get22.getUri()); } - public void testFlatListingWithDirectorySegmented() throws URISyntaxException, StorageException { + // Re-enable after fix in 3.0 with int->Integer + public void ignoreTestFlatListingWithDirectorySegmented() throws URISyntaxException, StorageException { for (int i = 0; i < delimiters.length; i++) { CloudBlobContainer container = null; try { 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 53579fe..bf180fa 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 @@ -134,6 +134,41 @@ public void testCopyBlockBlobTest() throws InvalidKeyException, URISyntaxExcepti InterruptedException { this.doCloudBlockBlobCopy(false, false); } + + public void testCopyWithChineseChars() throws StorageException, IOException, URISyntaxException { + String data = "sample data chinese chars 阿䶵"; + CloudBlockBlob copySource = container.getBlockBlobReference("sourcechinescharsblob阿䶵.txt"); + copySource.uploadText(data); + + assertEquals(this.container.getUri() + "/sourcechinescharsblob阿䶵.txt", copySource.getUri().toString()); + assertEquals(this.container.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", + copySource.getUri().toASCIIString()); + + CloudBlockBlob copyDestination = container.getBlockBlobReference("destchinesecharsblob阿䶵.txt"); + + assertEquals(this.container.getUri() + "/destchinesecharsblob阿䶵.txt", copyDestination.getUri().toString()); + assertEquals(this.container.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", + copyDestination.getUri().toASCIIString()); + + OperationContext ctx = new OperationContext(); + ctx.getSendingRequestEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); + + // Test the copy destination request url + assertEquals(CloudBlockBlobTests.this.container.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", + con.getURL().toString()); + + // Test the copy source request property + assertEquals(CloudBlockBlobTests.this.container.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", + con.getRequestProperty("x-ms-copy-source")); + } + }); + + copyDestination.startCopyFromBlob(copySource.getUri(), null, null, null, ctx); + copyDestination.startCopyFromBlob(copySource, null, null, null, ctx); + } public void testCopyBlockBlobWithMetadataOverride() throws URISyntaxException, StorageException, IOException, InterruptedException { diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java index 556f69c..b102275 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java @@ -670,14 +670,17 @@ public void testUploadPages() throws URISyntaxException, StorageException, IOExc String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName); blobRef.create(blobLengthToUse); + assertNull(blobRef.getProperties().getPageBlobSequenceNumber()); // Upload one page (page 0) ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); blobRef.uploadPages(inputStream, 0, 512); + assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber()); // Upload pages 2-4 inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); blobRef.uploadPages(inputStream, 2 * 512, 3 * 512); + assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber()); // Now, we expect the first 512 bytes of the blob to be the first 512 bytes of the random buffer (page 0) // the next 512 bytes should be 0 (page 1) @@ -729,10 +732,12 @@ public void testClearPages() throws URISyntaxException, StorageException, IOExce String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName); blobRef.create(blobLengthToUse); + assertNull(blobRef.getProperties().getPageBlobSequenceNumber()); // Upload one page (page 0) ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); blobRef.uploadPages(inputStream, 0, blobLengthToUse); + assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber()); try { blobRef.clearPages(0, 256); @@ -751,6 +756,7 @@ public void testClearPages() throws URISyntaxException, StorageException, IOExce } blobRef.clearPages(3 * 512, 2 * 512); + assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber()); byte[] result = new byte[blobLengthToUse]; blobRef.downloadToByteArray(result, 0); @@ -776,21 +782,26 @@ public void testResize() throws StorageException, URISyntaxException { blob.create(1024); assertEquals(1024, blob.getProperties().getLength()); + assertNull(blob.getProperties().getPageBlobSequenceNumber()); blob2.downloadAttributes(); assertEquals(1024, blob2.getProperties().getLength()); + assertNull(blob.getProperties().getPageBlobSequenceNumber()); blob2.getProperties().setContentType("text/plain"); blob2.uploadProperties(); blob.resize(2048); assertEquals(2048, blob.getProperties().getLength()); + assertNotNull(blob.getProperties().getPageBlobSequenceNumber()); blob.downloadAttributes(); assertEquals("text/plain", blob.getProperties().getContentType()); + assertNotNull(blob.getProperties().getPageBlobSequenceNumber()); blob2.downloadAttributes(); assertEquals(2048, blob2.getProperties().getLength()); + assertNotNull(blob.getProperties().getPageBlobSequenceNumber()); } public void testDownloadPages() throws StorageException, URISyntaxException, IOException { diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java index 22d2eed..4886adc 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/queue/CloudQueueTests.java @@ -46,6 +46,7 @@ /** * Queue Tests */ +@SuppressWarnings("deprecation") public class CloudQueueTests extends TestCase { private CloudQueue queue; @@ -677,11 +678,11 @@ public void testAddMessage() throws StorageException { public void testAddMessageUnicode() throws StorageException { ArrayList messages = new ArrayList(); - messages.add("Le débat sur l'identité nationale, l'idée du président Nicolas Sarkozy de déchoir des personnes d'origine étrangère de la nationalité française ... certains cas et les récentes mesures prises contre les Roms ont choqué les experts, qui rendront leurs conclusions le 27 août."); - messages.add("Ваш логин Yahoo! дает доступ к таким мощным инструментам связи, как электронная почта, отправка мгновенных сообщений, функции безопасности, в частности, антивирусные средства и блокировщик всплывающей рекламы, и избранное, например, фото и музыка в сети — все бесплат"); - messages.add("据新华社8月12日电 8月11日晚,舟曲境内再次出现强降雨天气,使特大山洪泥石流灾情雪上加霜。白龙江水在梨坝子村的交汇地带形成一个新的堰塞湖,水位比平时高出3米。甘肃省国土资源厅副厅长张国华当日22时许在新闻发布会上介绍,截至12日21时50分,舟曲堰塞湖堰塞体已消除,溃坝险情已消除,目前针对堰塞湖的主要工作是疏通河道。"); - messages.add("ל כולם\", הדהים יעלון, ויישר קו עם העדות שמסר ראש הממשלה, בנימין נתניהו, לוועדת טירקל. לדבריו, אכן השרים דנו רק בהיבטים התקשורתיים של עצירת המשט: \"בשביעייה לא התקיים דיון על האלטרנטיבות. עסקנו בהיבטים "); - messages.add("Prozent auf 0,5 Prozent. Im Vergleich zum Vorjahresquartal wuchs die deutsche Wirtschaft von Januar bis März um 2,1 Prozent. Auch das ist eine Korrektur nach oben, ursprünglich waren es hier 1,7 Prozent"); + messages.add("Le débat sur l'identité nationale, l'idée du président Nicolas Sarkozy de déchoir des personnes d'origine étrangère de la nationalité française ... certains cas et les récentes mesures prises contre les Roms ont choqué les experts, qui rendront leurs conclusions le 27 août."); + messages.add("Ваш логин Yahoo! дает доÑ�туп к таким мощным инÑ�трументам Ñ�вÑ�зи, как Ñ�лектроннаÑ� почта, отправка мгновенных Ñ�ообщений, функции безопаÑ�ноÑ�ти, в чаÑ�тноÑ�ти, антивируÑ�ные Ñ�редÑ�тва и блокировщик вÑ�плывающей рекламы, и избранное, например, фото и музыка в Ñ�ети — вÑ�е беÑ�плат"); + messages.add("æ�®æ–°å�Žç¤¾8月12日电 8月11日晚,舟曲境内å†�次出现强é™�雨天气,使特大山洪泥石æµ�ç�¾æƒ…雪上加霜。白龙江水在梨å��å­�æ�‘的交汇地带形æˆ�一个新的堰塞湖,水ä½�比平时高出3米。甘肃çœ�国土资æº�厅副厅长张国å�Žå½“æ—¥22时许在新闻å�‘布会上介ç»�,截至12æ—¥21æ—¶50分,舟曲堰塞湖堰塞体已消除,溃å��险情已消除,目å‰�针对堰塞湖的主è¦�工作是ç–�通河é�“。"); + messages.add("ל כול×�\", הדהי×� יעלון, ויישר קו ×¢×� העדות שמסר ר×�ש הממשלה, בנימין נתניהו, לוועדת טירקל. לדבריו, ×�כן השרי×� דנו רק בהיבטי×� התקשורתיי×� של עצירת המשט: \"בשביעייה ל×� התקיי×� דיון על ×”×�לטרנטיבות. עסקנו בהיבטי×� "); + messages.add("Prozent auf 0,5 Prozent. Im Vergleich zum Vorjahresquartal wuchs die deutsche Wirtschaft von Januar bis März um 2,1 Prozent. Auch das ist eine Korrektur nach oben, ursprünglich waren es hier 1,7 Prozent"); messages.add("\n\n\n\n Computer Parts\n \n Motherboard\n ASUS\n " + "P3B-F\n 123.00\n \n \n Video Card\n ATI\n All-in-Wonder Pro\n 160.00\n \n \n Sound Card\n " + "Creative Labs\n Sound Blaster Live\n 80.00\n \n \n inch Monitor\n LG Electronics\n 995E\n 290.00\n \n"); @@ -753,7 +754,7 @@ public void testAddMessageToNonExistingQueue() throws StorageException, URISynt public void testQueueUnicodeAndXmlMessageTest() throws StorageException { - String msgContent = "好"; + String msgContent = "好"; final CloudQueueMessage message = new CloudQueueMessage(msgContent); this.queue.addMessage(message); CloudQueueMessage msgFromRetrieve1 = this.queue.retrieveMessage(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/TableTestSuite.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/TableTestSuite.java index 757acf4..b27e080 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/TableTestSuite.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/runners/TableTestSuite.java @@ -19,6 +19,7 @@ import com.microsoft.azure.storage.table.TableBatchOperationTests; import com.microsoft.azure.storage.table.TableClientTests; +import com.microsoft.azure.storage.table.TableDateTests; import com.microsoft.azure.storage.table.TableEscapingTests; import com.microsoft.azure.storage.table.TableODataTests; import com.microsoft.azure.storage.table.TableOperationTests; @@ -31,6 +32,7 @@ public static Test suite() { TestSuite suite = new TestSuite("TableTestSuite"); suite.addTestSuite(TableBatchOperationTests.class); suite.addTestSuite(TableClientTests.class); + suite.addTestSuite(TableDateTests.class); suite.addTestSuite(TableEscapingTests.class); suite.addTestSuite(TableODataTests.class); suite.addTestSuite(TableOperationTests.class); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableDateTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableDateTests.java new file mode 100644 index 0000000..c497126 --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableDateTests.java @@ -0,0 +1,314 @@ +/** + * 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.table; + +import java.net.URISyntaxException; +import java.util.Date; +import junit.framework.TestCase; + +import com.microsoft.azure.storage.StorageException; + +/** + * Table Operation Tests + */ +public class TableDateTests extends TestCase { + + private CloudTable table; + + @Override + public void setUp() throws URISyntaxException, StorageException { + this.table = TableTestHelper.getRandomTableReference(); + this.table.createIfNotExists(); + } + + @Override + public void tearDown() throws StorageException { + this.table.deleteIfExists(); + } + + public void testTableQueryRoundTripDate() throws URISyntaxException, StorageException { + // 2014-12-07T09:15:12.123Z from Java + testTableQueryRoundTripDate(new Date(1417943712123L)); + + // 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); + this.table.execute(put); + + TableOperation get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class); + entity = this.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); + this.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class); + final TableRequestOptions options = new TableRequestOptions(); + options.setDateBackwardCompatibility(true); + entity = this.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); + this.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class); + dynamicEntity = this.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); + this.table.execute(put); + + get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class); + options.setDateBackwardCompatibility(true); + dynamicEntity = this.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); + this.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 = this.table.execute(get, options, null).getResultAsType(); + + // Ensure the date matches our expectations + assertEquals(expectedMilliseconds, dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime()); + } + + 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; + } + } +} \ No newline at end of file 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 821af11..3ad9faa 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 @@ -336,260 +336,6 @@ 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)); - - // 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(); @@ -930,24 +676,4 @@ 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/AndroidManifest.xml b/microsoft-azure-storage/AndroidManifest.xml index 24689fd..6223189 100644 --- a/microsoft-azure-storage/AndroidManifest.xml +++ b/microsoft-azure-storage/AndroidManifest.xml @@ -11,7 +11,7 @@ + android:versionName="0.5.0" > diff --git a/microsoft-azure-storage/pom.xml b/microsoft-azure-storage/pom.xml index c7a1cf8..cc7f18d 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.4.1 + 0.5.0 aar Microsoft Azure Storage Android Client SDK diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java index 5c12327..18e830f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/AccessCondition.java @@ -17,7 +17,7 @@ import java.net.HttpURLConnection; import java.util.Date; -import com.microsoft.azure.storage.core.BaseRequest; +import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.Utility; /** @@ -35,7 +35,7 @@ public final class AccessCondition { public static AccessCondition generateEmptyCondition() { return new AccessCondition(); } - + /** * Returns an access condition such that an operation will be performed only if the resource's ETag value matches * the specified ETag value. @@ -57,7 +57,7 @@ public static AccessCondition generateIfMatchCondition(final String etag) { retCondition.setIfMatch(etag); return retCondition; } - + /** * Returns an access condition such that an operation will be performed only if the resource has been modified since * the specified time. @@ -123,7 +123,52 @@ public static AccessCondition generateIfNotModifiedSinceCondition(final Date las retCondition.ifUnmodifiedSinceDate = lastMotified; return retCondition; } + + /** + * Returns an access condition such that an operation will be performed only if resource's current sequence + * number is less than or equal to the specified value. This condition only applies to page blobs. + * + * @param sequenceNumber + * The value to compare to the current sequence number. + * + * @return An AccessCondition object that represents the If-Sequence-Number-LE condition. + */ + public static AccessCondition generateIfSequenceNumberLessThanOrEqualCondition(long sequenceNumber) { + AccessCondition retCondition = new AccessCondition(); + retCondition.ifSequenceNumberLessThanOrEqual = sequenceNumber; + return retCondition; + } + + /** + * Returns an access condition such that an operation will be performed only if resource's current sequence + * number is less than the specified value. This condition only applies to page blobs. + * + * @param sequenceNumber + * The value to compare to the current sequence number. + * + * @return An AccessCondition object that represents the If-Sequence-Number-LT condition. + */ + public static AccessCondition generateIfSequenceNumberLessThanCondition(long sequenceNumber) { + AccessCondition retCondition = new AccessCondition(); + retCondition.ifSequenceNumberLessThan = sequenceNumber; + return retCondition; + } + /** + * Returns an access condition such that an operation will be performed only if resource's current sequence + * number is equal to the specified value. This condition only applies to page blobs. + * + * @param sequenceNumber + * The value to compare to the current sequence number. + * + * @return An AccessCondition object that represents the If-Sequence-Number-EQ condition. + */ + public static AccessCondition generateIfSequenceNumberEqualCondition(long sequenceNumber) { + AccessCondition retCondition = new AccessCondition(); + retCondition.ifSequenceNumberEqual = sequenceNumber; + return retCondition; + } + /** * Returns an access condition such that an operation will be performed only if the resource is accessible under the * specified lease ID. @@ -134,6 +179,7 @@ public static AccessCondition generateIfNotModifiedSinceCondition(final Date las * @param leaseID * The lease ID to specify. * + * @return An AccessCondition object that represents the lease condition. */ public static AccessCondition generateLeaseCondition(final String leaseID) { AccessCondition retCondition = new AccessCondition(); @@ -144,9 +190,14 @@ public static AccessCondition generateLeaseCondition(final String leaseID) { private String leaseID = null; /** - * Represents the ETag of the resource for if [none] match conditions + * Represents the ETag of the resource for if match conditions + */ + private String ifMatchETag = null; + + /** + * Represents the ETag of the resource for if none match conditions */ - private String etag = null; + private String ifNoneMatchETag = null; /** * Represents the date for ifModifiedSinceDate conditions. @@ -157,11 +208,21 @@ public static AccessCondition generateLeaseCondition(final String leaseID) { * Represents the date for ifUnmodifiedSinceDate conditions. */ private Date ifUnmodifiedSinceDate = null; - + + /** + * Represents the ifSequenceNumberLessThanOrEqual type. Used only for page blob operations. + */ + private Long ifSequenceNumberLessThanOrEqual = null; + /** - * Represents the ifMatchHeaderType type. + * Represents the ifSequenceNumberLessThan type. Used only for page blob operations. */ - private String ifMatchHeaderType = null; + private Long ifSequenceNumberLessThan = null; + + /** + * Represents the ifSequenceNumberEqual type. Used only for page blob operations. + */ + private Long ifSequenceNumberEqual = null; /** * Creates an instance of the AccessCondition class. @@ -171,87 +232,120 @@ public AccessCondition() { } /** - * RESERVED FOR INTERNAL USE. Applies the access condition to the request. + * RESERVED FOR INTERNAL USE. Applies the access conditions to the request. * * @param request - * A java.net.HttpURLConnection object that represents the request to which the condition is - * being applied. - * - * @throws StorageException - * If there is an error parsing the date value of the access condition. + * A java.net.HttpURLConnection object that represents the request + * to which the condition is being applied. */ public void applyConditionToRequest(final HttpURLConnection request) { - applyConditionToRequest(request, false); + applyLeaseConditionToRequest(request); + + if (this.ifModifiedSinceDate != null) { + request.setRequestProperty(Constants.HeaderConstants.IF_MODIFIED_SINCE, + Utility.getGMTTime(this.ifModifiedSinceDate)); + } + + if (this.ifUnmodifiedSinceDate != null) { + request.setRequestProperty(Constants.HeaderConstants.IF_UNMODIFIED_SINCE, + Utility.getGMTTime(this.ifUnmodifiedSinceDate)); + } + + if (!Utility.isNullOrEmpty(this.ifMatchETag)) { + request.setRequestProperty(Constants.HeaderConstants.IF_MATCH, this.ifMatchETag); + } + + if (!Utility.isNullOrEmpty(this.ifNoneMatchETag)) { + request.setRequestProperty(Constants.HeaderConstants.IF_NONE_MATCH, this.ifNoneMatchETag); + } } /** - * RESERVED FOR INTERNAL USE. Applies the access condition to the request. + * RESERVED FOR INTERNAL USE. Applies the source access conditions to the request. * * @param request - * A java.net.HttpURLConnection object that represents the request to which the condition is - * being applied. - * @param useSourceAccessHeaders - * If true will use the Source_ headers for the conditions, otherwise standard headers are used. - * @throws StorageException - * If there is an error parsing the date value of the access condition. - */ - public void applyConditionToRequest(final HttpURLConnection request, boolean useSourceAccessHeaders) { - // When used as a source access condition - if (useSourceAccessHeaders) { - if (!Utility.isNullOrEmpty(this.leaseID)) { - request.setRequestProperty(Constants.HeaderConstants.SOURCE_LEASE_ID_HEADER, this.leaseID); - } - - if (this.ifModifiedSinceDate != null) { - request.setRequestProperty(Constants.HeaderConstants.SOURCE_IF_MODIFIED_SINCE_HEADER, - Utility.getGMTTime(this.ifModifiedSinceDate)); - } - - if (this.ifUnmodifiedSinceDate != null) { - request.setRequestProperty(Constants.HeaderConstants.SOURCE_IF_UNMODIFIED_SINCE_HEADER, - Utility.getGMTTime(this.ifUnmodifiedSinceDate)); - } + * A java.net.HttpURLConnection object that represents the request + * to which the condition is being applied. + */ + public void applySourceConditionToRequest(final HttpURLConnection request) { + if (!Utility.isNullOrEmpty(this.leaseID)) { + // Unsupported + throw new IllegalArgumentException(SR.LEASE_CONDITION_ON_SOURCE); + } - if (!Utility.isNullOrEmpty(this.etag)) { - if (this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_MATCH)) { - request.setRequestProperty(Constants.HeaderConstants.SOURCE_IF_MATCH_HEADER, this.etag); - } - // test - else if (this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_NONE_MATCH)) { - request.setRequestProperty(Constants.HeaderConstants.SOURCE_IF_NONE_MATCH_HEADER, this.etag); - } - } + if (this.ifModifiedSinceDate != null) { + request.setRequestProperty( + Constants.HeaderConstants.SOURCE_IF_MODIFIED_SINCE_HEADER, + Utility.getGMTTime(this.ifModifiedSinceDate)); } - else { - if (!Utility.isNullOrEmpty(this.leaseID)) { - BaseRequest.addLeaseId(request, this.leaseID); - } - if (this.ifModifiedSinceDate != null) { - request.setRequestProperty(Constants.HeaderConstants.IF_MODIFIED_SINCE, - Utility.getGMTTime(this.ifModifiedSinceDate)); - } + if (this.ifUnmodifiedSinceDate != null) { + request.setRequestProperty(Constants.HeaderConstants.SOURCE_IF_UNMODIFIED_SINCE_HEADER, + Utility.getGMTTime(this.ifUnmodifiedSinceDate)); + } - if (this.ifUnmodifiedSinceDate != null) { - request.setRequestProperty(Constants.HeaderConstants.IF_UNMODIFIED_SINCE, - Utility.getGMTTime(this.ifUnmodifiedSinceDate)); - } + if (!Utility.isNullOrEmpty(this.ifMatchETag)) { + request.setRequestProperty( + Constants.HeaderConstants.SOURCE_IF_MATCH_HEADER, + this.ifMatchETag); + } - if (!Utility.isNullOrEmpty(this.etag)) { - request.setRequestProperty(this.ifMatchHeaderType, this.etag); - } + if (!Utility.isNullOrEmpty(this.ifNoneMatchETag)) { + request.setRequestProperty( + Constants.HeaderConstants.SOURCE_IF_NONE_MATCH_HEADER, + this.ifNoneMatchETag); } } + + /** + * RESERVED FOR INTERNAL USE. Applies the lease access condition to the request. + * + * @param request + * A java.net.HttpURLConnection object that represents the request + * to which the condition is being applied. + */ + public void applyLeaseConditionToRequest(final HttpURLConnection request) { + if (!Utility.isNullOrEmpty(this.leaseID)) { + request.setRequestProperty(Constants.HeaderConstants.LEASE_ID_HEADER, this.leaseID); + } + } + + /** + * RESERVED FOR INTERNAL USE. Applies the sequence number access conditions to the request. + * + * @param request + * A java.net.HttpURLConnection object that represents the request + * to which the condition is being applied. + */ + public void applySequenceConditionToRequest(final HttpURLConnection request) { + if (this.ifSequenceNumberLessThanOrEqual != null) { + request.setRequestProperty( + Constants.HeaderConstants.IF_SEQUENCE_NUMBER_LESS_THAN_OR_EQUAL, + this.ifSequenceNumberLessThanOrEqual.toString()); + } + if (this.ifSequenceNumberLessThan != null) { + request.setRequestProperty( + Constants.HeaderConstants.IF_SEQUENCE_NUMBER_LESS_THAN, + this.ifSequenceNumberLessThan.toString()); + } + + if (this.ifSequenceNumberEqual != null) { + request.setRequestProperty( + Constants.HeaderConstants.IF_SEQUENCE_NUMBER_EQUAL, + this.ifSequenceNumberEqual.toString()); + } + } + /** * Gets the ETag when the If-Match condition is set. * * @return The ETag when the If-Match condition is set; otherwise, null. */ public String getIfMatch() { - return this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_MATCH) ? this.etag : null; + return this.ifMatchETag; } - + /** * Gets the If-Modified-Since date. * @@ -267,7 +361,7 @@ public Date getIfModifiedSinceDate() { * @return The ETag when the If-None-Match condition is set; otherwise, null. */ public String getIfNoneMatch() { - return this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_NONE_MATCH) ? this.etag : null; + return this.ifNoneMatchETag; } /** @@ -287,6 +381,36 @@ public Date getIfUnmodifiedSinceDate() { public String getLeaseID() { return this.leaseID; } + + /** + * Gets the sequence number when the sequence number less than or equal condition is set. This condition + * is only applicable to page blobs. + * + * @return The sequence number when the ifSequenceNumberLessThanOrEqual condition is set; otherwise, null + */ + public Long getIfSequenceNumberLessThanOrEqual() { + return this.ifSequenceNumberLessThanOrEqual; + } + + /** + * Gets the sequence number when the sequence number less than condition is set. This condition + * is only applicable to page blobs. + * + * @return The sequence number when the ifSequenceNumberLessThan condition is set; otherwise, null + */ + public Long getIfSequenceNumberLessThan() { + return this.ifSequenceNumberLessThan; + } + + /** + * Gets the sequence number when the sequence number equal condition is set. This condition + * is only applicable to page blobs. + * + * @return The sequence number when the ifSequenceNumberEqual condition is set; otherwise, null + */ + public Long getIfSequenceNumberEqual() { + return this.ifSequenceNumberEqual; + } /** * Sets the ETag for the If-Match condition. @@ -295,8 +419,7 @@ public String getLeaseID() { * The ETag to set for the If-Match condition. */ public void setIfMatch(String etag) { - this.etag = normalizeEtag(etag); - this.ifMatchHeaderType = Constants.HeaderConstants.IF_MATCH; + this.ifMatchETag = normalizeEtag(etag); } /** @@ -316,8 +439,7 @@ public void setIfModifiedSinceDate(Date ifModifiedSinceDate) { * The ETag to set for the If-None-Match condition. */ public void setIfNoneMatch(String etag) { - this.etag = normalizeEtag(etag); - this.ifMatchHeaderType = Constants.HeaderConstants.IF_NONE_MATCH; + this.ifNoneMatchETag = normalizeEtag(etag); } /** @@ -341,7 +463,40 @@ public void setLeaseID(String leaseID) { } /** - * Reserved for internal use. Verifies the condition is satisfied. + * Sets the sequence number for the sequence number less than or equal to condition. This condition + * is only applicable to page blobs. + * + * @param sequenceNumber + * The sequence number to set the if sequence number less than or equal condition to. + */ + public void setIfSequenceNumberLessThanOrEqual(Long sequenceNumber) { + this.ifSequenceNumberLessThanOrEqual = sequenceNumber; + } + + /** + * Sets the sequence number for the sequence number less than condition. This condition + * is only applicable to page blobs. + * + * @param sequenceNumber + * The sequence number to set the if sequence number less than condition to. + */ + public void setIfSequenceNumberLessThan(Long sequenceNumber) { + this.ifSequenceNumberLessThan = sequenceNumber; + } + + /** + * Sets the sequence number for the sequence number equal to condition. This condition + * is only applicable to page blobs. + * + * @param sequenceNumber + * The sequence number to set the if sequence number equal condition to. + */ + public void setIfSequenceNumberEqual(Long sequenceNumber) { + this.ifSequenceNumberEqual = sequenceNumber; + } + + /** + * RESERVED FOR INTERNAL USE. Verifies the condition is satisfied. * * @param etag * A String that represents the ETag to check. @@ -365,20 +520,19 @@ public boolean verifyConditional(final String etag, final Date lastModified) { return false; } } - - if (!Utility.isNullOrEmpty(this.etag)) { - if (this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_MATCH)) { - if (!this.etag.equals(etag) && !this.etag.equals("*")) { - return false; - } + + if (!Utility.isNullOrEmpty(this.ifMatchETag)) { + if (!this.ifMatchETag.equals(etag) && !this.ifMatchETag.equals("*")) { + return false; } - else if (this.ifMatchHeaderType.equals(Constants.HeaderConstants.IF_NONE_MATCH)) { - if (this.etag.equals(etag)) { - return false; - } + } + + if (!Utility.isNullOrEmpty(this.ifNoneMatchETag)) { + if (this.ifNoneMatchETag.equals(etag)) { + return false; } } - + return true; } 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 7c47a85..7d82739 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -234,6 +234,11 @@ public static class HeaderConstants { */ public static final String BEGIN_RANGE_HEADER_FORMAT = "bytes=%d-"; + /** + * The header that specifies blob sequence number. + */ + public static final String BLOB_SEQUENCE_NUMBER = PREFIX_FOR_STORAGE_HEADER + "blob-sequence-number"; + /** * The CacheControl header. */ @@ -369,7 +374,22 @@ public static class HeaderConstants { * The IfUnmodifiedSince header. */ public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; - + + /** + * The blob sequence number less than or equal condition header. + */ + public static final String IF_SEQUENCE_NUMBER_LESS_THAN_OR_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-le"; + + /** + * The blob sequence number less than condition header. + */ + public static final String IF_SEQUENCE_NUMBER_LESS_THAN = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-lt"; + + /** + * The blob sequence number equal condition header. + */ + public static final String IF_SEQUENCE_NUMBER_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-eq"; + /** * The header that specifies the lease action to perform */ @@ -515,7 +535,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "0.4.1"; + public static final String USER_AGENT_VERSION = "0.5.0"; /** * The default type for content-type and accept 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 25fc514..1211623 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ServiceClient.java @@ -304,18 +304,18 @@ public void recoveryAction(OperationContext context) throws IOException { return putRequest; } catch (IllegalArgumentException e) { - // The request was not even made. There was an error while trying to write the service properties. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { - // The request was not even made. There was an error while trying to write the service properties. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { - // The request was not even made. There was an error while trying to write the service properties. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java index 1586004..d466cb1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageException.java @@ -14,79 +14,34 @@ */ package com.microsoft.azure.storage; -import java.io.InputStreamReader; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.SocketException; -import com.microsoft.azure.storage.table.TablePayloadFormat; -import com.microsoft.azure.storage.table.TableStorageErrorDeserializer; +import com.microsoft.azure.storage.core.StorageRequest; +import com.microsoft.azure.storage.core.Utility; /** * Represents an exception for the Microsoft Azure storage service. */ public class StorageException extends Exception { - /** * Represents the serialization version number. */ private static final long serialVersionUID = 7972747254288274928L; /** - * Returns extended error information from the specified request and operation context. - * - * @param request - * An HttpURLConnection object that represents the request whose extended error information - * is being retrieved. - * @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 StorageExtendedErrorInformation} object that represents the error details for the specified - * request. - */ - protected static StorageExtendedErrorInformation getErrorDetailsFromRequest(final HttpURLConnection request, - final OperationContext opContext) { - if (request == null || request.getErrorStream() == null) { - return null; - } - try { - return StorageErrorHandler.getExtendedErrorInformation(request.getErrorStream()); - } - catch (final Exception e) { - return null; - } - } - - /** - * Returns extended error information from the specified request and operation context. + * RESERVED FOR INTERNAL USE. Translates the specified exception into a storage exception. * - * @param request - * An HttpURLConnection object that represents the request whose extended error information - * is being retrieved. - * @param format - * The {@link TablePayloadFormat} format to be used when parsing the table request error - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. + * @param cause + * An Exception object that represents the exception to translate. * - * @return A {@link StorageExtendedErrorInformation} object that represents the error details for the specified - * request. + * @return A StorageException object that represents translated exception. */ - protected static StorageExtendedErrorInformation getErrorDetailsFromTableRequest(final HttpURLConnection request, - final TablePayloadFormat format, final OperationContext opContext) { - if (request == null || request.getErrorStream() == null) { - return null; - } - - try { - return TableStorageErrorDeserializer.getExtendedErrorInformation( - new InputStreamReader(request.getErrorStream()), format); - } - catch (Exception e) { - return null; - } + public static StorageException translateClientException(final Exception cause) { + return new StorageException("Client error", + "A Client side exception occurred, please check the inner exception for details", + Constants.HeaderConstants.HTTP_UNUSED_306, null, cause); } /** @@ -97,54 +52,30 @@ protected static StorageExtendedErrorInformation getErrorDetailsFromTableRequest * translated. * @param cause * An Exception object that represents the exception to translate. - * @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 StorageException object that represents translated exception. */ - public static StorageException translateException(final HttpURLConnection request, final Exception cause, + public static StorageException translateException(final StorageRequest request, final Exception cause, final OperationContext opContext) { - if (request == null) { - return new StorageException("Client error", - "A Client side exception occurred, please check the inner exception for details", - Constants.HeaderConstants.HTTP_UNUSED_306, null, cause); + if (request == null || request.getConnection() == null) { + return translateClientException(cause); } - + if (cause instanceof SocketException) { + String message = cause == null ? Constants.EMPTY_STRING : cause.getMessage(); return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(), - "An unknown failure occurred : ".concat(cause.getMessage()), HttpURLConnection.HTTP_INTERNAL_ERROR, + "An unknown failure occurred : ".concat(message), HttpURLConnection.HTTP_INTERNAL_ERROR, null, cause); } - StorageExtendedErrorInformation extendedError = null; - - try { - final String server = request.getHeaderField("Server"); - if (server != null && server.startsWith("Windows-Azure-Table")) { - final String type = request.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE); - if (type != null && type.startsWith("application/json")) { - extendedError = getErrorDetailsFromTableRequest(request, TablePayloadFormat.Json, opContext); - } - } - else { - extendedError = getErrorDetailsFromRequest(request, opContext); - } - } - catch (Exception e) { - // do nothing and continue, we want to get as much error info as we can - } - StorageException translatedException = null; - String responseMessage = Constants.EMPTY_STRING; + String responseMessage = null; int responseCode = 0; try { - responseCode = request.getResponseCode(); - responseMessage = request.getResponseMessage(); - } - catch (final Exception e) { + responseCode = request.getConnection().getResponseCode(); + responseMessage = request.getConnection().getResponseMessage(); + } catch (final IOException e) { // ignore errors } @@ -152,29 +83,25 @@ public static StorageException translateException(final HttpURLConnection reques responseMessage = Constants.EMPTY_STRING; } - // 1. If extended information is available use it + StorageExtendedErrorInformation extendedError = request.parseErrorDetails(); if (extendedError != null) { + // 1. If extended information is available use it translatedException = new StorageException(extendedError.getErrorCode(), responseMessage, responseCode, extendedError, cause); - - if (translatedException != null) { - return translatedException; - } + } else { + // 2. If extended information is unavailable, translate exception based + // on status code + translatedException = translateFromHttpStatus(responseCode, responseMessage, cause); } - // 2. If extended information is unavailable, translate exception based - // on status code - translatedException = translateFromHttpStatus(responseCode, responseMessage, null, cause); - if (translatedException != null) { + Utility.logHttpError(translatedException, opContext); return translatedException; + } else { + return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(), + "The server encountered an unknown failure: ".concat(responseMessage), + HttpURLConnection.HTTP_INTERNAL_ERROR, null, cause); } - - // 3. If extended information is not available and the status code is unknown, - // create a storage exception with the cause and message provided - return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(), - "The server encountered an unknown failure: ".concat(responseMessage), - HttpURLConnection.HTTP_INTERNAL_ERROR, null, cause); } /** @@ -193,60 +120,62 @@ public static StorageException translateException(final HttpURLConnection reques * @return A StorageException object that represents translated exception. **/ protected static StorageException translateFromHttpStatus(final int statusCode, final String statusDescription, - final StorageExtendedErrorInformation details, final Exception inner) { + final Exception inner) { + String errorCode; switch (statusCode) { - case HttpURLConnection.HTTP_FORBIDDEN: - return new StorageException(StorageErrorCode.ACCESS_DENIED.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_GONE: - case HttpURLConnection.HTTP_NOT_FOUND: - return new StorageException(StorageErrorCode.RESOURCE_NOT_FOUND.toString(), statusDescription, - statusCode, details, inner); - - case HttpURLConnection.HTTP_BAD_REQUEST: - return new StorageException(StorageErrorCode.BAD_REQUEST.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_PRECON_FAILED: - case HttpURLConnection.HTTP_NOT_MODIFIED: - return new StorageException(StorageErrorCode.CONDITION_FAILED.toString(), statusDescription, - statusCode, details, inner); - - case HttpURLConnection.HTTP_CONFLICT: - return new StorageException(StorageErrorCode.RESOURCE_ALREADY_EXISTS.toString(), statusDescription, - statusCode, details, inner); - - case HttpURLConnection.HTTP_UNAVAILABLE: - return new StorageException(StorageErrorCode.SERVER_BUSY.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_GATEWAY_TIMEOUT: - return new StorageException(StorageErrorCode.SERVICE_TIMEOUT.toString(), statusDescription, statusCode, - details, inner); - - case 416: - // RequestedRangeNotSatisfiable - No corresponding enum in HttpURLConnection - return new StorageException(StorageErrorCode.BAD_REQUEST.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_INTERNAL_ERROR: - return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(), statusDescription, - statusCode, details, inner); - - case HttpURLConnection.HTTP_NOT_IMPLEMENTED: - return new StorageException(StorageErrorCode.NOT_IMPLEMENTED.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_BAD_GATEWAY: - return new StorageException(StorageErrorCode.BAD_GATEWAY.toString(), statusDescription, statusCode, - details, inner); - - case HttpURLConnection.HTTP_VERSION: - return new StorageException(StorageErrorCode.HTTP_VERSION_NOT_SUPPORTED.toString(), statusDescription, - statusCode, details, inner); - default: - return null; + case HttpURLConnection.HTTP_FORBIDDEN: + errorCode = StorageErrorCode.ACCESS_DENIED.toString(); + break; + case HttpURLConnection.HTTP_GONE: + case HttpURLConnection.HTTP_NOT_FOUND: + errorCode = StorageErrorCode.RESOURCE_NOT_FOUND.toString(); + break; + case 416: + case HttpURLConnection.HTTP_BAD_REQUEST: + // 416: RequestedRangeNotSatisfiable - No corresponding enum in HttpURLConnection + errorCode = StorageErrorCode.BAD_REQUEST.toString(); + break; + + case HttpURLConnection.HTTP_PRECON_FAILED: + case HttpURLConnection.HTTP_NOT_MODIFIED: + errorCode = StorageErrorCode.CONDITION_FAILED.toString(); + break; + + case HttpURLConnection.HTTP_CONFLICT: + errorCode = StorageErrorCode.RESOURCE_ALREADY_EXISTS.toString(); + break; + + case HttpURLConnection.HTTP_UNAVAILABLE: + errorCode = StorageErrorCode.SERVER_BUSY.toString(); + break; + + case HttpURLConnection.HTTP_GATEWAY_TIMEOUT: + errorCode = StorageErrorCode.SERVICE_TIMEOUT.toString(); + break; + + case HttpURLConnection.HTTP_INTERNAL_ERROR: + errorCode = StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(); + break; + + case HttpURLConnection.HTTP_NOT_IMPLEMENTED: + errorCode = StorageErrorCode.NOT_IMPLEMENTED.toString(); + break; + + case HttpURLConnection.HTTP_BAD_GATEWAY: + errorCode = StorageErrorCode.BAD_GATEWAY.toString(); + break; + + case HttpURLConnection.HTTP_VERSION: + errorCode = StorageErrorCode.HTTP_VERSION_NOT_SUPPORTED.toString(); + break; + default: + errorCode = null; + } + + if (errorCode == null) { + return null; + } else { + return new StorageException(errorCode, statusDescription, statusCode, null, inner); } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java index 8f92938..7d73233 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobOutputStream.java @@ -269,17 +269,9 @@ public void close() throws IOException { // flush any remaining data this.flush(); - // Shut down the ExecutorService. Executes previously submitted tasks, but accepts no new tasks. + // shut down the ExecutorService. this.threadExecutor.shutdown(); - // Waits for all submitted tasks to complete - while (this.outstandingRequests > 0) { - this.waitForTaskToComplete(); - } - - // if one of the tasks threw an exception, realize it now. - this.checkStreamState(); - // try to commit the blob try { this.commit(); @@ -441,7 +433,17 @@ public synchronized void flush() throws IOException { */ } + // Dispatch a write for the current bytes in the buffer this.dispatchWrite(this.currentBufferedBytes); + + // Waits for all submitted tasks to complete + while (this.outstandingRequests > 0) { + // Wait for a task to complete + this.waitForTaskToComplete(); + + // If that task threw an error, fail fast + this.checkStreamState(); + } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java index c999cfc..a80e561 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java @@ -96,7 +96,12 @@ public final class BlobProperties { * Represents the size, in bytes, of the blob. */ private long length; - + + /** + * Represents the page blob's current sequence number. + */ + private Long pageBlobSequenceNumber; + /** * Creates an instance of the BlobProperties class. */ @@ -126,6 +131,7 @@ public BlobProperties(final BlobProperties other) { this.lastModified = other.lastModified; this.contentMD5 = other.contentMD5; this.cacheControl = other.cacheControl; + this.pageBlobSequenceNumber = other.pageBlobSequenceNumber; } /** @@ -273,6 +279,15 @@ public long getLength() { return this.length; } + /** + * If the blob is a page blob, gets the page blob's current sequence number. + * + * @return A Long containing the page blob's current sequence number. + */ + public Long getPageBlobSequenceNumber() { + return this.pageBlobSequenceNumber; + } + /** * Sets the cache control value for the blob. * @@ -412,5 +427,14 @@ protected void setLeaseDuration(final LeaseDuration leaseDuration) { protected void setLength(final long length) { this.length = length; } - + + /** + * If the blob is a page blob, sets the blob's current sequence number. + * + * @param pageBlobSequenceNumber + * A long containing the blob's current sequence number. + */ + protected void setPageBlobSequenceNumber(final Long pageBlobSequenceNumber) { + this.pageBlobSequenceNumber = pageBlobSequenceNumber; + } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java index faf6998..6adeb92 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobRequest.java @@ -97,7 +97,7 @@ public static HttpURLConnection abortCopy(final URI uri, final BlobRequestOption Constants.HeaderConstants.COPY_ACTION_ABORT); if (accessCondition != null) { - accessCondition.applyConditionToRequest(request, true); + accessCondition.applyLeaseConditionToRequest(request); } return request; @@ -198,7 +198,7 @@ public static HttpURLConnection copyFrom(final URI uri, final BlobRequestOptions request.setRequestProperty(Constants.HeaderConstants.COPY_SOURCE_HEADER, source); if (sourceAccessCondition != null) { - sourceAccessCondition.applyConditionToRequest(request, true); + sourceAccessCondition.applySourceConditionToRequest(request); } if (destinationAccessCondition != null) { @@ -376,8 +376,8 @@ public static HttpURLConnection getAcl(final URI uri, final BlobRequestOptions b request.setRequestMethod(Constants.HTTP_GET); - if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { - BaseRequest.addLeaseId(request, accessCondition.getLeaseID()); + if (accessCondition != null) { + accessCondition.applyLeaseConditionToRequest(request); } return request; @@ -648,8 +648,8 @@ private static HttpURLConnection getProperties(final URI uri, final BlobRequestO throws IOException, URISyntaxException, StorageException { HttpURLConnection request = BaseRequest.getProperties(uri, blobOptions, builder, opContext); - if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { - BaseRequest.addLeaseId(request, accessCondition.getLeaseID()); + if (accessCondition != null) { + accessCondition.applyLeaseConditionToRequest(request); } return request; @@ -946,24 +946,8 @@ public static HttpURLConnection listBlobs(final URI uri, final BlobRequestOption public static HttpURLConnection listContainers(final URI uri, final BlobRequestOptions blobOptions, final OperationContext opContext, final ListingContext listingContext, final ContainerListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException { - - final UriQueryBuilder builder = getContainerUriQueryBuilder(); - builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); - - if (listingContext != null) { - if (!Utility.isNullOrEmpty(listingContext.getPrefix())) { - builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix()); - } - - if (!Utility.isNullOrEmpty(listingContext.getMarker())) { - builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker()); - } - - if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) { - builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString()); - } - } - + final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); + if (detailsIncluded == ContainerListingDetails.ALL || detailsIncluded == ContainerListingDetails.METADATA) { builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA); } @@ -1180,6 +1164,7 @@ public static HttpURLConnection putPage(final URI uri, final BlobRequestOptions if (accessCondition != null) { accessCondition.applyConditionToRequest(request); + accessCondition.applySequenceConditionToRequest(request); } return request; @@ -1270,8 +1255,8 @@ public static HttpURLConnection setAcl(final URI uri, final BlobRequestOptions b request.setRequestProperty(BlobConstants.BLOB_PUBLIC_ACCESS_HEADER, publicAccess.toString().toLowerCase()); } - if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { - BaseRequest.addLeaseId(request, accessCondition.getLeaseID()); + if (accessCondition != null) { + accessCondition.applyLeaseConditionToRequest(request); } return request; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java index a3a20d8..1972af9 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java @@ -99,7 +99,13 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { properties.setLength(Long.parseLong(contentLength)); } } - + + // Get sequence number + final String sequenceNumber = request.getHeaderField(Constants.HeaderConstants.BLOB_SEQUENCE_NUMBER); + if (!Utility.isNullOrEmpty(sequenceNumber)) { + properties.setPageBlobSequenceNumber(Long.parseLong(sequenceNumber)); + } + attributes.setStorageUri(resourceURI); attributes.setSnapshotID(snapshotID); 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 6e7dced..072bd31 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 @@ -770,18 +770,15 @@ private StorageRequest startCopyFromBlobImpl final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, final BlobRequestOptions options) { - if (sourceAccessCondition != null && !Utility.isNullOrEmpty(sourceAccessCondition.getLeaseID())) { - throw new IllegalArgumentException(SR.LEASE_CONDITION_ON_SOURCE); - } - final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { + // toASCIIString() must be used in order to appropriately encode the URI return BlobRequest.copyFrom(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, sourceAccessCondition, destinationAccessCondition, source.toString(), + options, context, sourceAccessCondition, destinationAccessCondition, source.toASCIIString(), blob.snapshotID); } @@ -2754,103 +2751,6 @@ protected void updateLengthFromResponse(HttpURLConnection request) { public abstract void upload(InputStream sourceStream, long length, final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException; - /** - * Uploads a blob in a single operation. - * - * @param sourceStream - * A InputStream object that represents the source stream to upload. - * @param length - * The length, in bytes, of the stream, or -1 if unknown. - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the blob. - * @param options - * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudBlobClient}). - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - protected final void uploadFullBlob(final InputStream sourceStream, final long length, - final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) - throws StorageException { - assertNoWriteOperationForSnapshot(); - - // Mark sourceStream for current position. - sourceStream.mark(Constants.MAX_MARK_LENGTH); - - if (length < 0 || length > BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES) { - throw new IllegalArgumentException(String.format(SR.INVALID_STREAM_LENGTH, - BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES / Constants.MB)); - } - - ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - uploadFullBlobImpl(sourceStream, length, accessCondition, options, opContext), - options.getRetryPolicyFactory(), opContext); - } - - private StorageRequest uploadFullBlobImpl(final InputStream sourceStream, - final long length, final AccessCondition accessCondition, final BlobRequestOptions options, - final OperationContext opContext) { - final StorageRequest putRequest = new StorageRequest( - options, this.getStorageUri()) { - - @Override - public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) - throws Exception { - this.setSendStream(sourceStream); - this.setLength(length); - return BlobRequest.putBlob(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()), - options, opContext, accessCondition, blob.properties, blob.properties.getBlobType(), - this.getLength()); - } - - @Override - public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) { - BlobRequest.addMetadata(connection, blob.metadata, opContext); - } - - @Override - public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) - throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); - } - - @Override - public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) - throws Exception { - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { - this.setNonExceptionedRetryableFailure(true); - return null; - } - - blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - return null; - } - - @Override - public void recoveryAction(OperationContext context) throws IOException { - sourceStream.reset(); - sourceStream.mark(Constants.MAX_MARK_LENGTH); - } - - @Override - public void validateStreamWrite(StreamMd5AndLength descriptor) throws StorageException { - if (this.getLength() != null && this.getLength() != -1) { - if (length != descriptor.getLength()) { - throw new StorageException(StorageErrorCodeStrings.INVALID_INPUT, SR.INCORRECT_STREAM_LENGTH, - HttpURLConnection.HTTP_FORBIDDEN, null, null); - } - } - } - }; - - return putRequest; - } /** * Uploads the blob's metadata to the storage service. 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 b7953be..3119e1e 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 @@ -1688,17 +1688,17 @@ public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient cli } catch (IllegalArgumentException e) { // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } 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 4f5164d..4ae81b8 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 @@ -26,6 +26,7 @@ import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.Base64; @@ -307,17 +308,17 @@ public void recoveryAction(OperationContext context) throws IOException { } catch (IllegalArgumentException e) { // The request was not even made. There was an error while trying to write the block list. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { // The request was not even made. There was an error while trying to write the block list. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { // The request was not even made. There was an error while trying to write the block list. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } @@ -584,6 +585,104 @@ public void upload(final InputStream sourceStream, final long length, final Acce } } + /** + * Uploads a blob in a single operation. + * + * @param sourceStream + * A InputStream object that represents the source stream to upload. + * @param length + * The length, in bytes, of the stream, or -1 if unknown. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + protected final void uploadFullBlob(final InputStream sourceStream, final long length, + final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) + throws StorageException { + assertNoWriteOperationForSnapshot(); + + // Mark sourceStream for current position. + sourceStream.mark(Constants.MAX_MARK_LENGTH); + + if (length < 0 || length > BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES) { + throw new IllegalArgumentException(String.format(SR.INVALID_STREAM_LENGTH, + BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES / Constants.MB)); + } + + ExecutionEngine.executeWithRetry(this.blobServiceClient, this, + uploadFullBlobImpl(sourceStream, length, accessCondition, options, opContext), + options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest uploadFullBlobImpl(final InputStream sourceStream, + final long length, final AccessCondition accessCondition, final BlobRequestOptions options, + final OperationContext opContext) { + final StorageRequest putRequest = new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + throws Exception { + this.setSendStream(sourceStream); + this.setLength(length); + return BlobRequest.putBlob(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()), + options, opContext, accessCondition, blob.properties, blob.properties.getBlobType(), + this.getLength()); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) { + BlobRequest.addMetadata(connection, blob.metadata, opContext); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, length, null); + } + + @Override + public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + return null; + } + + @Override + public void recoveryAction(OperationContext context) throws IOException { + sourceStream.reset(); + sourceStream.mark(Constants.MAX_MARK_LENGTH); + } + + @Override + public void validateStreamWrite(StreamMd5AndLength descriptor) throws StorageException { + if (this.getLength() != null && this.getLength() != -1) { + if (length != descriptor.getLength()) { + throw new StorageException(StorageErrorCodeStrings.INVALID_INPUT, SR.INCORRECT_STREAM_LENGTH, + HttpURLConnection.HTTP_FORBIDDEN, null, null); + } + } + } + }; + + return putRequest; + } + /** * Uploads a block to be committed as part of the block blob, using the specified block ID. * 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 7a5e7f2..99a5ef2 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 @@ -344,6 +344,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + blob.getProperties().setLength(length); return null; } @@ -634,14 +635,14 @@ private void putPagesInternal(final PageRange pageRange, final PageOperationType options.getRetryPolicyFactory(), opContext); } - private StorageRequest putPagesImpl(final PageRange pageRange, + private StorageRequest putPagesImpl(final PageRange pageRange, final PageOperationType operationType, final byte[] data, final long length, final String md5, final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) { - final StorageRequest putRequest = new StorageRequest( + final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override - public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + public HttpURLConnection buildRequest(CloudBlobClient client, CloudPageBlob blob, OperationContext context) throws Exception { if (operationType == PageOperationType.UPDATE) { this.setSendStream(new ByteArrayInputStream(data)); @@ -653,7 +654,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op } @Override - public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) { + public void setHeaders(HttpURLConnection connection, CloudPageBlob blob, OperationContext context) { if (operationType == PageOperationType.UPDATE) { if (options.getUseTransactionalContentMD5()) { connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, md5); @@ -673,7 +674,7 @@ public void signRequest(HttpURLConnection connection, CloudBlobClient client, Op } @Override - public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { this.setNonExceptionedRetryableFailure(true); @@ -681,12 +682,20 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + blob.updateSequenceNumberFromResponse(this.getConnection()); return null; } }; return putRequest; } + + protected void updateSequenceNumberFromResponse(HttpURLConnection request) { + final String sequenceNumber = request.getHeaderField(Constants.HeaderConstants.BLOB_SEQUENCE_NUMBER); + if (!Utility.isNullOrEmpty(sequenceNumber)) { + this.getProperties().setPageBlobSequenceNumber(Long.parseLong(sequenceNumber)); + } + } /** * Resizes the page blob to the specified size. @@ -739,13 +748,13 @@ public void resize(long size, AccessCondition accessCondition, BlobRequestOption options.getRetryPolicyFactory(), opContext); } - private StorageRequest resizeImpl(final long size, + private StorageRequest resizeImpl(final long size, final AccessCondition accessCondition, final BlobRequestOptions options) { - final StorageRequest putRequest = new StorageRequest( + final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override - public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + public HttpURLConnection buildRequest(CloudBlobClient client, CloudPageBlob blob, OperationContext context) throws Exception { return BlobRequest.resize(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, accessCondition, size); @@ -758,7 +767,7 @@ public void signRequest(HttpURLConnection connection, CloudBlobClient client, Op } @Override - public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { this.setNonExceptionedRetryableFailure(true); @@ -767,6 +776,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation blob.getProperties().setLength(size); blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + blob.updateSequenceNumberFromResponse(this.getConnection()); return null; } }; @@ -860,8 +870,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce * An {@link IntputStream} object which represents the input stream to write to the page blob. * @param offset * A long which represents the offset, in number of bytes, at which to begin writing the - * data. This value must be a multiple of - * 512. + * data. This value must be a multiple of 512. * @param length * A long which represents the length, in bytes, of the data to write. This value must be a * multiple of 512. 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 919cdeb..536c53b 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 @@ -48,20 +48,6 @@ public final class BaseRequest { */ private static String userAgent; - /** - * Adds the lease id. - * - * @param request - * a HttpURLConnection for the operation. - * @param leaseId - * the lease id to add to the HttpURLConnection. - */ - public static void addLeaseId(final HttpURLConnection request, final String leaseId) { - if (leaseId != null) { - BaseRequest.addOptionalHeader(request, Constants.HeaderConstants.LEASE_ID_HEADER, leaseId); - } - } - /** * Adds the metadata. * @@ -125,7 +111,8 @@ public static void addOptionalHeader(final HttpURLConnection request, final Stri * @param uri * the request Uri. * @param options - * TODO + * A {@link RequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. * @param builder * the UriQueryBuilder for the request * @param opContext @@ -242,6 +229,37 @@ public static HttpURLConnection delete(final URI uri, final RequestOptions optio return retConnection; } + /** + * Gets a {@link UriQueryBuilder} for listing. + * + * @param listingContext + * A {@link ListingContext} object that specifies parameters for + * the listing operation, if any. May be null. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + public static UriQueryBuilder getListUriQueryBuilder(final ListingContext listingContext) throws StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); + + if (listingContext != null) { + if (!Utility.isNullOrEmpty(listingContext.getPrefix())) { + builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix()); + } + + if (!Utility.isNullOrEmpty(listingContext.getMarker())) { + builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker()); + } + + if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) { + builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString()); + } + } + + return builder; + } + /** * Gets the properties. Sign with no length specified. * @@ -349,7 +367,7 @@ public static String getUserAgent() { return userAgent; } - + /** * Sets the metadata. Sign with 0 length. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java index 5e47236..cbf85af 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/ExecutionEngine.java @@ -114,6 +114,8 @@ public static RESULT_TYPE executeWithRet Logger.info(opContext, LogConstants.UPLOADDONE); } + Utility.logHttpRequest(request, opContext); + // 6. Process the request - Get response RequestResult currResult = task.getResult(); currResult.setStartDate(new Date()); @@ -133,7 +135,10 @@ public static RESULT_TYPE executeWithRet ExecutionEngine.fireResponseReceivedEvent(opContext, request, task.getResult()); Logger.info(opContext, LogConstants.RESPONSE_RECEIVED, currResult.getStatusCode(), - currResult.getServiceRequestID(), currResult.getContentMD5(), currResult.getEtag()); + currResult.getServiceRequestID(), currResult.getContentMD5(), currResult.getEtag(), + currResult.getRequestDate()); + + Utility.logHttpResponse(request, opContext); // 8. Pre-process response to check if there was an exception. Do Response parsing (headers etc). Logger.info(opContext, LogConstants.PRE_PROCESS); @@ -173,7 +178,7 @@ public static RESULT_TYPE executeWithRet else { Logger.warn(opContext, LogConstants.UNEXPECTED_RESULT_OR_EXCEPTION); // The task may have already parsed an exception. - translatedException = task.materializeException(task.getConnection(), opContext); + translatedException = task.materializeException(opContext); task.getResult().setException(translatedException); // throw on non retryable status codes: 501, 505, blob type mismatch @@ -187,7 +192,7 @@ public static RESULT_TYPE executeWithRet catch (final TimeoutException e) { // Retryable Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = StorageException.translateException(task.getConnection(), e, opContext); + translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); } catch (final SocketTimeoutException e) { @@ -210,7 +215,7 @@ public static RESULT_TYPE executeWithRet } else { Logger.warn(opContext, LogConstants.RETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); - translatedException = StorageException.translateException(task.getConnection(), e, opContext); + translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); } } @@ -224,14 +229,14 @@ public static RESULT_TYPE executeWithRet } catch (final InvalidKeyException e) { // Non Retryable, just throw - translatedException = StorageException.translateException(task.getConnection(), e, opContext); + translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); throw translatedException; } catch (final URISyntaxException e) { // Non Retryable, just throw - translatedException = StorageException.translateException(task.getConnection(), e, opContext); + translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); throw translatedException; @@ -259,7 +264,7 @@ public static RESULT_TYPE executeWithRet } catch (final Exception e) { // Non Retryable, just throw - translatedException = StorageException.translateException(task.getConnection(), e, opContext); + translatedException = StorageException.translateException(task, e, opContext); task.getResult().setException(translatedException); Logger.error(opContext, LogConstants.UNRETRYABLE_EXCEPTION, e.getClass().getName(), e.getMessage()); throw translatedException; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LazySegmentedIterator.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LazySegmentedIterator.java index c4a4c31..4a7fddd 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LazySegmentedIterator.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LazySegmentedIterator.java @@ -129,7 +129,11 @@ public boolean hasNext() { */ @Override public ENTITY_TYPE next() { - return this.currentSegmentIterator.next(); + if (this.hasNext()) { + return this.currentSegmentIterator.next(); + } else { + throw new NoSuchElementException(); + } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java index 911ba92..8ce774d 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/LogConstants.java @@ -29,7 +29,7 @@ public class LogConstants { public static final String POST_PROCESS_DONE = "Response body was parsed successfully."; public static final String PRE_PROCESS = "Processing response headers."; public static final String PRE_PROCESS_DONE = "Response headers were processed successfully."; - public static final String RESPONSE_RECEIVED = "Response received. Status code = '%d', Request ID = '%s', Content-MD5 = '%s', ETag = '%s'."; + public static final String RESPONSE_RECEIVED = "Response received. Status code = '%d', Request ID = '%s', Content-MD5 = '%s', ETag = '%s', Date = '%s'."; public static final String RETRY = "Retrying failed operation."; public static final String RETRY_CHECK = "Checking if the operation should be retried. Retry count = '%d', HTTP status code = '%d', Error Message = '%s'."; public static final String RETRY_DELAY = "Operation will be retried after '%d'ms."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Logger.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Logger.java index 5e9b81a..1e0e343 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Logger.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Logger.java @@ -147,7 +147,7 @@ public static void warn(OperationContext opContext, String format, Object arg1, } } - private static boolean shouldLog(OperationContext opContext, int logLevel) { + public static boolean shouldLog(OperationContext opContext, int logLevel) { if (opContext != null && opContext.getLogLevel() != null) { return opContext.getLogLevel() <= logLevel && Log.isLoggable(Constants.LOG_TAG, logLevel); } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageErrorHandler.java similarity index 95% rename from microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorHandler.java rename to microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageErrorHandler.java index 85a4dca..f07d2d1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageErrorHandler.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.microsoft.azure.storage; +package com.microsoft.azure.storage.core; import java.io.IOException; import java.io.InputStream; @@ -25,8 +25,8 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import com.microsoft.azure.storage.core.SR; -import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; /** * RESERVED FOR INTERNAL USE. A class used to deserialize storage errors. 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 cc0f9d4..7d94f45 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 @@ -28,6 +28,7 @@ import com.microsoft.azure.storage.RequestResult; import com.microsoft.azure.storage.ServiceClient; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.StorageLocation; import com.microsoft.azure.storage.StorageUri; @@ -298,13 +299,12 @@ public final boolean isNonExceptionedRetryableFailure() { * an object used to track the execution of the operation * @return the exception to throw. */ - protected final StorageException materializeException(final HttpURLConnection request, - final OperationContext opContext) { + protected final StorageException materializeException(final OperationContext opContext) { if (this.getException() != null) { return this.getException(); } - return StorageException.translateException(request, null, opContext); + return StorageException.translateException(this, null, opContext); } public static final void signBlobQueueAndFileRequest(HttpURLConnection request, ServiceClient client, @@ -632,4 +632,22 @@ public void validateStreamWrite(StreamMd5AndLength descriptor) throws StorageExc public void recoveryAction(OperationContext context) throws IOException { // no-op } + + /** + * Returns extended error information for this request. + * + * @return A {@link StorageExtendedErrorInformation} object that represents the error details for the specified + * request. + */ + public StorageExtendedErrorInformation parseErrorDetails() { + try { + if (this.getConnection() == null || this.getConnection().getErrorStream() == null) { + return null; + } + + return StorageErrorHandler.getExtendedErrorInformation(this.getConnection().getErrorStream()); + } catch (final Exception e) { + return null; + } + } } 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 ed5d053..bc4dd0f 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 @@ -35,6 +35,8 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; import java.util.TimeZone; import java.util.concurrent.TimeoutException; @@ -45,6 +47,7 @@ import org.xml.sax.SAXException; import org.xmlpull.v1.XmlSerializer; +import android.util.Log; import android.util.Xml; import com.microsoft.azure.storage.Constants; @@ -59,6 +62,7 @@ import com.microsoft.azure.storage.StorageErrorCode; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; /** * RESERVED FOR INTERNAL USE. A class which provides utility methods. @@ -922,6 +926,135 @@ else if (basePath.charAt(m) == '/') { } } + /** + * Serializes the parsed StorageException. If an exception is encountered, returns empty string. + * + * @param ex + * The StorageException to serialize. + * @param opContext + * The operation context which provides the logger. + */ + public static void logHttpError(StorageException ex, OperationContext opContext) { + if (Logger.shouldLog(opContext, Log.DEBUG)) { + try { + StringBuilder bld = new StringBuilder(); + bld.append("Error response received. "); + + bld.append("HttpStatusCode= "); + bld.append(ex.getHttpStatusCode()); + + bld.append(", HttpStatusMessage= "); + bld.append(ex.getMessage()); + + bld.append(", ErrorCode= "); + bld.append(ex.getErrorCode()); + + StorageExtendedErrorInformation extendedError = ex.getExtendedErrorInformation(); + if (extendedError != null) { + bld.append(", ExtendedErrorInformation= {ErrorMessage= "); + bld.append(extendedError.getErrorMessage()); + + HashMap details = extendedError.getAdditionalDetails(); + if (details != null) { + bld.append(", AdditionalDetails= { "); + for (Entry detail : details.entrySet()) { + bld.append(detail.getKey()); + bld.append("= "); + + for (String value : detail.getValue()) { + bld.append(value); + } + bld.append(","); + } + bld.setCharAt(bld.length() - 1, '}'); + } + bld.append("}"); + } + + Logger.debug(opContext, bld.toString()); + } catch (Exception e) { + // Do nothing + } + } + } + + /** + * Logs the HttpURLConnection request. If an exception is encountered, logs nothing. + * + * @param conn + * The HttpURLConnection to serialize. + * @param opContext + * The operation context which provides the logger. + */ + public static void logHttpRequest(HttpURLConnection conn, OperationContext opContext) throws IOException { + if (Logger.shouldLog(opContext, Log.VERBOSE)) { + try { + StringBuilder bld = new StringBuilder(); + + bld.append(conn.getRequestMethod()); + bld.append(" "); + bld.append(conn.getURL()); + bld.append("\n"); + + // The Authorization header will not appear due to a security feature in HttpURLConnection + for (Map.Entry> header : conn.getRequestProperties().entrySet()) { + if (header.getKey() != null) { + bld.append(header.getKey()); + bld.append(": "); + } + + for (int i = 0; i < header.getValue().size(); i++) { + bld.append(header.getValue().get(i)); + if (i < header.getValue().size() - 1) { + bld.append(","); + } + } + bld.append('\n'); + } + + Logger.verbose(opContext, bld.toString()); + } catch (Exception e) { + // Do nothing + } + } + } + + /** + * Logs the HttpURLConnection response. If an exception is encountered, logs nothing. + * + * @param conn + * The HttpURLConnection to serialize. + * @param opContext + * The operation context which provides the logger. + */ + public static void logHttpResponse(HttpURLConnection conn, OperationContext opContext) throws IOException { + if (Logger.shouldLog(opContext, Log.VERBOSE)) { + try { + StringBuilder bld = new StringBuilder(); + + // This map's null key will contain the response code and message + for (Map.Entry> header : conn.getHeaderFields().entrySet()) { + if (header.getKey() != null) { + bld.append(header.getKey()); + bld.append(": "); + } + + for (int i = 0; i < header.getValue().size(); i++) { + bld.append(header.getValue().get(i)); + if (i < header.getValue().size() - 1) { + bld.append(","); + } + } + bld.append('\n'); + } + + Logger.verbose(opContext, bld.toString()); + } catch (Exception e) { + // Do nothing + } + } + } + /** * Trims the specified character from the end of a string. * 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 faddcad..9d0b33f 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 @@ -343,7 +343,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, Void parentObject, @Override public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); //TODO + StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, null); } @Override diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileOutputStream.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileOutputStream.java index eaf0ea0..db6b41e 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileOutputStream.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileOutputStream.java @@ -196,17 +196,9 @@ public void close() throws IOException { // flush any remaining data this.flush(); - // Shut down the ExecutorService. Executes previously submitted tasks, but accepts no new tasks. + // shut down the ExecutorService. this.threadExecutor.shutdown(); - // Waits for all submitted tasks to complete - while (this.outstandingRequests > 0) { - this.waitForTaskToComplete(); - } - - // if one of the tasks threw an exception, realize it now. - this.checkStreamState(); - // try to commit the file try { this.commit(); @@ -318,7 +310,18 @@ public Void call() { @DoesServiceRequest public synchronized void flush() throws IOException { this.checkStreamState(); + + // Dispatch a write for the current bytes in the buffer this.dispatchWrite(this.currentBufferedBytes); + + // Waits for all submitted tasks to complete + while (this.outstandingRequests > 0) { + // Wait for a task to complete + this.waitForTaskToComplete(); + + // If that task threw an error, fail fast + this.checkStreamState(); + } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java index 8ff9117..f440198 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java @@ -384,8 +384,8 @@ private static HttpURLConnection getProperties(final URI uri, final FileRequestO throws IOException, URISyntaxException, StorageException { HttpURLConnection request = BaseRequest.getProperties(uri, fileOptions, builder, opContext); - if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) { - BaseRequest.addLeaseId(request, accessCondition.getLeaseID()); + if (accessCondition != null) { + accessCondition.applyLeaseConditionToRequest(request); } return request; @@ -419,22 +419,7 @@ public static HttpURLConnection listShares(final URI uri, final FileRequestOptio final OperationContext opContext, final ListingContext listingContext, final ShareListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException { - final UriQueryBuilder builder = getShareUriQueryBuilder(); - builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); - - if (listingContext != null) { - if (!Utility.isNullOrEmpty(listingContext.getPrefix())) { - builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix()); - } - - if (!Utility.isNullOrEmpty(listingContext.getMarker())) { - builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker()); - } - - if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) { - builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString()); - } - } + final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); if (detailsIncluded == ShareListingDetails.ALL || detailsIncluded == ShareListingDetails.METADATA) { builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java index 6ef39f7..33478a4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/CloudQueue.java @@ -325,17 +325,17 @@ public Void preProcessResponse(CloudQueue parentObject, CloudQueueClient client, } catch (IllegalArgumentException e) { // The request was not even made. There was an error while trying to write the message. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { // The request was not even made. There was an error while trying to write the message. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { // The request was not even made. There was an error while trying to write the message. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } @@ -1427,18 +1427,18 @@ public HttpURLConnection buildRequest(CloudQueueClient client, CloudQueue queue, this.setLength((long) messageBytes.length); } catch (IllegalArgumentException e) { - // There was an error while trying to write the message. Wrap it and throw. - StorageException translatedException = StorageException.translateException(null, e, context); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { - // There was an error while trying to write the message. Wrap it and throw. - StorageException translatedException = StorageException.translateException(null, e, context); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { - // There was an error while trying to write the message. Wrap it and throw. - StorageException translatedException = StorageException.translateException(null, e, context); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } @@ -1647,18 +1647,18 @@ public Void preProcessResponse(CloudQueue parentObject, CloudQueueClient client, return putRequest; } catch (IllegalArgumentException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequest.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequest.java index 3ab367e..102c21a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/queue/QueueRequest.java @@ -27,7 +27,6 @@ import com.microsoft.azure.storage.core.BaseRequest; import com.microsoft.azure.storage.core.ListingContext; import com.microsoft.azure.storage.core.UriQueryBuilder; -import com.microsoft.azure.storage.core.Utility; /** * RESERVED FOR INTERNAL USE. Provides a set of methods for constructing web @@ -275,23 +274,7 @@ public static HttpURLConnection downloadAttributes(final URI uri, final QueueReq public static HttpURLConnection list(final URI uri, final QueueRequestOptions queueOptions, final OperationContext opContext, final ListingContext listingContext, final QueueListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException { - - final UriQueryBuilder builder = new UriQueryBuilder(); - builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); - - if (listingContext != null) { - if (!Utility.isNullOrEmpty(listingContext.getPrefix())) { - builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix()); - } - - if (!Utility.isNullOrEmpty(listingContext.getMarker())) { - builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker()); - } - - if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) { - builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString()); - } - } + final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); if (detailsIncluded == QueueListingDetails.ALL || detailsIncluded == QueueListingDetails.METADATA) { builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java index 2b0553f..103ca85 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/CloudTable.java @@ -37,6 +37,7 @@ import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.PathUtility; @@ -996,23 +997,28 @@ public Void preProcessResponse(CloudTable parentObject, CloudTableClient client, return null; } + + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return putRequest; } catch (IllegalArgumentException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IllegalStateException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (IOException e) { - // The request was not even made. There was an error while trying to write the permissions. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + // The request was not even made. There was an error while trying to write the message. Just throw. + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } @@ -1104,7 +1110,11 @@ public TablePermissions postProcessResponse(HttpURLConnection connection, CloudT return permissions; } - + + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return getRequest; 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 36a1fbe..568965d 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 @@ -34,6 +34,7 @@ import com.microsoft.azure.storage.ServiceStats; import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.blob.BlobRequestOptions; import com.microsoft.azure.storage.blob.CloudBlobClient; @@ -437,6 +438,11 @@ public ResultSegment postProcessResponse(HttpURLConnection connection, TableQ queryToExecute.getTakeCount() == null ? clazzResponse.results.size() : queryToExecute.getTakeCount(), nextToken); } + + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return getRequest; @@ -508,6 +514,11 @@ public ResultSegment postProcessResponse(HttpURLConnection connection, TableQ : queryToExecute.getTakeCount(), nextToken); } + + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return getRequest; 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 13cb215..885f75f 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 @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonParseException; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; @@ -236,6 +237,10 @@ public TableResult postProcessResponse(HttpURLConnection connection, QueryTableO return res; } + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return getRequest; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java index 4814aa8..f9af572 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableBatchOperation.java @@ -29,6 +29,7 @@ import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; @@ -563,18 +564,23 @@ currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload), return result; } + + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return batchRequest; } catch (IOException e) { // The request was not even made. There was an error while trying to read the batch contents. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } catch (URISyntaxException e) { // The request was not even made. There was an error while trying to read the batch contents. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } 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 b741a48..12982d3 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 @@ -25,6 +25,7 @@ import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.StorageExtendedErrorInformation; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.StorageRequest; @@ -333,6 +334,10 @@ public TableResult preProcessResponse(TableOperation operation, CloudTableClient return operation.parseResponse(null, this.getResult().getStatusCode(), null, opContext, options); } + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return deleteRequest; @@ -475,13 +480,17 @@ public TableResult postProcessResponse(HttpURLConnection connection, TableOperat return result; } + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return putRequest; } catch (IOException e) { // The request was not even made. There was an error while trying to read the entity. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } @@ -571,13 +580,17 @@ public TableResult preProcessResponse(TableOperation operation, CloudTableClient } } + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return putRequest; } catch (IOException e) { // The request was not even made. There was an error while trying to read the entity. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } @@ -666,13 +679,17 @@ public TableResult preProcessResponse(TableOperation operation, CloudTableClient } } + @Override + public StorageExtendedErrorInformation parseErrorDetails() { + return TableStorageErrorDeserializer.parseErrorDetails(this); + } }; return putRequest; } catch (IOException e) { // The request was not even made. There was an error while trying to read the entity. Just throw. - StorageException translatedException = StorageException.translateException(null, e, null); + StorageException translatedException = StorageException.translateClientException(e); throw translatedException; } } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableStorageErrorDeserializer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableStorageErrorDeserializer.java index d999d6f..e707e2f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableStorageErrorDeserializer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/table/TableStorageErrorDeserializer.java @@ -16,6 +16,7 @@ package com.microsoft.azure.storage.table; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.util.HashMap; @@ -25,11 +26,12 @@ import com.fasterxml.jackson.core.JsonToken; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.StorageExtendedErrorInformation; +import com.microsoft.azure.storage.core.StorageRequest; /*** * RESERVED FOR INTERNAL USE. A class to help parse the error details from an input stream, specific to tables */ -public final class TableStorageErrorDeserializer { +final class TableStorageErrorDeserializer { /** * Gets the Extended Error information. @@ -56,6 +58,28 @@ public static StorageExtendedErrorInformation getExtendedErrorInformation(final } } + /** + * Parse the table extended error information from the response body. + * + * @param request + * the request whose body to examine for the error information. + * @return The {@link StorageExtendedErrorInformation} parsed from the body or null if parsing fails or the request + * contains no body. + */ + public static StorageExtendedErrorInformation parseErrorDetails(StorageRequest request) { + try { + if (request == null || request.getConnection().getErrorStream() == null) { + return null; + } + + return getExtendedErrorInformation(new InputStreamReader( + request.getConnection().getErrorStream()), + TablePayloadFormat.Json); + } catch (Exception e) { + return null; + } + } + /** * Parses the error exception details from the Json-formatted response. *