From 0c5df282ff7093583146da4a03571a2ecbcb1334 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 10 Feb 2017 14:41:27 -0800 Subject: [PATCH 01/30] Share Snapshot Support --- ChangeLog.txt | 4 + .../storage/file/CloudFileClientTests.java | 51 ++- .../storage/file/CloudFileDirectoryTests.java | 81 ++++- .../storage/file/CloudFileShareTests.java | 198 ++++++++++- .../azure/storage/file/CloudFileTests.java | 203 ++++++++--- .../azure/storage/file/FileTestHelper.java | 6 +- .../microsoft/azure/storage/Constants.java | 14 +- .../storage/StorageErrorCodeStrings.java | 5 + .../azure/storage/blob/BlobConstants.java | 5 - .../azure/storage/blob/BlobRequest.java | 2 +- .../com/microsoft/azure/storage/core/SR.java | 1 + .../core/SharedAccessSignatureHelper.java | 8 +- .../azure/storage/file/CloudFile.java | 153 ++++++--- .../azure/storage/file/CloudFileClient.java | 55 ++- .../storage/file/CloudFileDirectory.java | 77 +++-- .../azure/storage/file/CloudFileShare.java | 323 +++++++++++++++++- .../file/DeleteShareSnapshotsOption.java | 35 ++ .../azure/storage/file/FileOutputStream.java | 8 +- .../azure/storage/file/FileRequest.java | 148 +++++++- .../azure/storage/file/FileResponse.java | 11 + .../storage/file/FileShareProperties.java | 25 +- .../azure/storage/file/ShareListHandler.java | 6 + .../storage/file/ShareListingDetails.java | 8 +- 23 files changed, 1230 insertions(+), 197 deletions(-) create mode 100644 microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 95cd884..c724746 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,7 @@ +2017.XX.XX Version 2.0.0 + * Added support for taking a snapshot of a share. + * Fixed Exists() calls on Shares and Directories to now populate metadata. This was already being done for Files. + 2017.01.30 Version 1.0.0 * Android Studio support via Gradle build system. * Updated tests from JUnit 3 to Android Junit 4. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java index 77db677..d17a3c0 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java @@ -27,6 +27,8 @@ import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; import java.util.UUID; import static org.junit.Assert.*; @@ -63,8 +65,8 @@ public void testListSharesTest() throws StorageException, URISyntaxException { ResultContinuation token = null; do { - ResultSegment segment = fileClient.listSharesSegmented(prefix, ShareListingDetails.ALL, - 15, token, null, null); + ResultSegment segment = fileClient.listSharesSegmented(prefix, + EnumSet.allOf(ShareListingDetails.class), 15, token, null, null); for (final CloudFileShare share : segment.getResults()) { share.downloadAttributes(); @@ -101,7 +103,7 @@ public void testListSharesMaxResultsValidationTest() throws StorageException, UR for (int i = 0; i >= -2; i--) { try{ fileClient.listSharesSegmented( - prefix, ShareListingDetails.ALL, i, null, null, null); + prefix, EnumSet.allOf(ShareListingDetails.class), i, null, null, null); fail(); } catch (IllegalArgumentException e) { @@ -111,4 +113,47 @@ public void testListSharesMaxResultsValidationTest() throws StorageException, UR } assertNotNull(fileClient.listSharesSegmented("thereshouldntbeanyshareswiththisprefix")); } + + @Test + public void testListSharesWithSnapshot() throws StorageException, URISyntaxException { + CloudFileClient fileClient = FileTestHelper.createCloudFileClient(); + CloudFileShare share = fileClient.getShareReference(UUID.randomUUID().toString()); + share.create(); + + HashMap shareMeta = new HashMap(); + shareMeta.put("key1", "value1"); + share.setMetadata(shareMeta); + share.uploadMetadata(); + + CloudFileShare snapshot = share.createSnapshot(); + HashMap meta2 = new HashMap(); + meta2.put("key2", "value2"); + share.setMetadata(meta2); + share.uploadMetadata(); + + CloudFileClient client = FileTestHelper.createCloudFileClient(); + Iterable listResult = client.listShares(share.name, EnumSet.allOf(ShareListingDetails.class), null, null); + + int count = 0; + boolean originalFound = false; + boolean snapshotFound = false; + for (CloudFileShare listShareItem : listResult) { + if (listShareItem.getName().equals(share.getName()) && !listShareItem.isSnapshot() && !originalFound) + { + count++; + originalFound = true; + assertEquals(share.getMetadata(), listShareItem.getMetadata()); + assertEquals(share.getStorageUri(), listShareItem.getStorageUri()); + } + else if (listShareItem.getName().equals(share.getName()) && + listShareItem.isSnapshot() && !snapshotFound) { + count++; + snapshotFound = true; + assertEquals(snapshot.getMetadata(), listShareItem.getMetadata()); + assertEquals(snapshot.getStorageUri(), listShareItem.getStorageUri()); + } + } + + assertEquals(2, count); + } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java index da5373b..7d6df9a 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java @@ -26,19 +26,21 @@ import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.core.PathUtility; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.UriQueryBuilder; import junit.framework.Assert; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; - import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.junit.Assert.*; @@ -460,7 +462,7 @@ public void testCloudFileDirectoryInvalidMetadata() throws StorageException, URI testMetadataFailures(directory, "key1", "\n \t", false); } - private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) { + private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) throws URISyntaxException { directory.getMetadata().put(key, value); try { directory.uploadMetadata(); @@ -478,6 +480,71 @@ private static void testMetadataFailures(CloudFileDirectory directory, String ke directory.getMetadata().remove(key); } + @Test + public void testUnsupportedDirectoryApisWithinShareSnapshot() throws StorageException, URISyntaxException { + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFileDirectory rootDir = snapshot.getRootDirectoryReference(); + try { + rootDir.create(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + rootDir.delete(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + rootDir.uploadMetadata(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + + snapshot.delete(); + } + + @Test + public void testSupportedDirectoryApisInShareSnapshot() throws StorageException, URISyntaxException { + CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); + dir.deleteIfExists(); + dir.create(); + HashMap meta = new HashMap(); + meta.put("key1", "value1"); + dir.setMetadata(meta); + dir.uploadMetadata(); + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFileDirectory snapshotDir = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); + + HashMap meta2 = new HashMap(); + meta2.put("key2", "value2"); + dir.setMetadata(meta2); + dir.uploadMetadata(); + snapshotDir.downloadAttributes(); + + assertTrue(snapshotDir.getMetadata().size() == 1 && snapshotDir.getMetadata().get("key1").equals("value1")); + assertNotNull(snapshotDir.getProperties().getEtag()); + + dir.downloadAttributes(); + assertTrue(dir.getMetadata().size() == 1 && dir.getMetadata().get("key2").equals("value2")); + assertNotNull(dir.getProperties().getEtag()); + assertNotEquals(dir.getProperties().getEtag(), snapshotDir.getProperties().getEtag()); + + final UriQueryBuilder uriBuilder = new UriQueryBuilder(); + uriBuilder.add("sharesnapshot", snapshot.snapshotID); + uriBuilder.add("restype", "directory"); + CloudFileDirectory snapshotDir2 = new CloudFileDirectory(uriBuilder.addToURI(dir.getUri()), this.share.getServiceClient().getCredentials()); + assertEquals(snapshot.snapshotID, snapshotDir2.getShare().snapshotID); + assertTrue(snapshotDir2.exists()); + + snapshot.delete(); + } + /* [TestMethod] [Description("CloudFileDirectory deleting a directory using conditional access")] @@ -800,6 +867,8 @@ public void eventOccurred(SendingRequestEvent eventArg) { } catch (StorageException e) { fail("Delete should succeed."); + } catch (URISyntaxException e) { + fail("Delete should succeed."); } } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java index 49df9d4..6147210 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java @@ -25,12 +25,14 @@ import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.TestRunners.SlowTests; import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.UriQueryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URISyntaxException; @@ -49,7 +51,6 @@ @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public class CloudFileShareTests { - protected static CloudFileClient client; protected CloudFileShare share; @Before @@ -59,7 +60,7 @@ public void fileShareTestMethodSetUp() throws StorageException, URISyntaxExcepti @After public void fileShareTestMethodTearDown() throws StorageException { - this.share.deleteIfExists(); + //this.share.deleteIfExists(); } /** @@ -314,7 +315,7 @@ public void testCloudFileShareUploadMetadata() throws StorageException, URISynta assertEquals("value2", this.share.getMetadata().get("key2")); Iterable shares = this.share.getServiceClient().listShares(this.share.getName(), - ShareListingDetails.METADATA, null, null); + EnumSet.of(ShareListingDetails.METADATA), null, null); for (CloudFileShare share3 : shares) { assertEquals(2, share3.getMetadata().size()); @@ -425,6 +426,7 @@ public void testCloudFileShareQuota() throws StorageException, URISyntaxExceptio try { shareQuota = FileConstants.MAX_SHARE_QUOTA + 1; this.share.getProperties().setShareQuota(shareQuota); + this.share.uploadProperties(); fail(); } catch (IllegalArgumentException e) { assertEquals(String.format(SR.PARAMETER_NOT_IN_RANGE, "Share Quota", 1, FileConstants.MAX_SHARE_QUOTA), @@ -489,6 +491,196 @@ public void eventOccurred(SendingRequestEvent eventArg) { assertTrue(this.share.deleteIfExists(null, null, ctx)); } + @Test + public void testCreateShareSnapshot() throws StorageException, URISyntaxException, IOException { + // create share with metadata + this.share.create(); + assertTrue(this.share.exists()); + HashMap shareMeta = new HashMap(); + shareMeta.put("key1", "value1"); + this.share.setMetadata(shareMeta); + this.share.uploadMetadata(); + + CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); + dir1.create(); + CloudFile file1 = dir1.getFileReference("file1"); + file1.create(1024); + ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(1024); + file1.upload(srcStream, 1024); + + // create directory with metadata + HashMap dirMeta = new HashMap(); + dirMeta.put("key2", "value2"); + dir1.setMetadata(dirMeta); + dir1.uploadMetadata(); + + // verify that exists() call on snapshot populates metadata + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFileClient client = FileTestHelper.createCloudFileClient(); + CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); + assertTrue(snapshotRef.exists()); + assertTrue(snapshotRef.getMetadata().size() == 1 && snapshotRef.getMetadata().get("key1").equals("value1")); + + // verify that downloadAttributes() populates metadata + CloudFileShare snapshotRef2 = client.getShareReference(snapshot.name, snapshot.snapshotID); + snapshotRef2.downloadAttributes(); + snapshot.downloadAttributes(); + assertTrue(snapshotRef2.getMetadata().size() == 1 && snapshotRef2.getMetadata().get("key1").equals("value1")); + assertTrue(snapshot.getMetadata().size() == 1 && snapshot.getMetadata().get("key1").equals("value1")); + + // verify that exists() populates the metadata + CloudFileDirectory snapshotDir1 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); + snapshotDir1.exists(); + assertTrue(snapshotDir1.getMetadata().size() == 1 && snapshotDir1.getMetadata().get("key2").equals("value2")); + + // verify that downloadAttributes() populates the metadata + CloudFileDirectory snapshotDir2 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); + snapshotDir2.downloadAttributes(); + assertTrue(snapshotDir2.getMetadata().size() == 1 && snapshotDir2.getMetadata().get("key2").equals("value2")); + + // create snapshot with metadata + HashMap shareMeta2 = new HashMap(); + shareMeta2.put("abc", "def"); + CloudFileShare snapshotRef3 = this.share.createSnapshot(shareMeta2, null, null, null); + CloudFileShare snapshotRef4 = client.getShareReference(snapshotRef3.name, snapshotRef3.snapshotID); + assertTrue(snapshotRef4.exists()); + assertTrue(snapshotRef4.getMetadata().size() == 1 && snapshotRef4.getMetadata().get("abc").equals("def")); + + final UriQueryBuilder uriBuilder = new UriQueryBuilder(); + uriBuilder.add("sharesnapshot", snapshot.snapshotID); + CloudFileShare snapshotRef5 = new CloudFileShare(uriBuilder.addToURI(this.share.getUri()), + this.share.getServiceClient().getCredentials(), null); + assertEquals(snapshot.snapshotID, snapshotRef5.snapshotID); + assertTrue(snapshotRef5.exists()); + + snapshot.delete(); + } + + @Test + public void testDeleteShareSnapshotOptions() throws StorageException, URISyntaxException, IOException { + // create share with metadata + this.share.create(); + assertTrue(this.share.exists()); + + // verify that exists() call on snapshot populates metadata + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFileClient client = FileTestHelper.createCloudFileClient(); + CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); + assertTrue(snapshotRef.exists()); + + try { + share.delete(); + } + catch (final StorageException e) { + assertEquals(StorageErrorCodeStrings.SHARE_HAS_SNAPSHOTS, e.getErrorCode()); + } + + share.delete(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null); + assertFalse(share.exists()); + assertFalse(snapshot.exists()); + } + + @Test + public void testListFilesAndDirectoriesWithinShareSnapshot() throws StorageException, URISyntaxException { + this.share.create(); + + CloudFileDirectory myDir = this.share.getRootDirectoryReference().getDirectoryReference("mydir"); + myDir.create(); + myDir.getFileReference("myfile").create(1024); + myDir.getDirectoryReference("yourDir").create(); + assertTrue(this.share.exists()); + + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFileClient client = FileTestHelper.createCloudFileClient(); + CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); + + Iterable listResult = snapshotRef.getRootDirectoryReference().listFilesAndDirectories(); + int count = 0; + for (ListFileItem listFileItem : listResult) { + count++; + assertEquals("mydir", ((CloudFileDirectory) listFileItem).getName()); + } + + assertEquals(1, count); + + count = 0; + listResult = snapshotRef.getRootDirectoryReference().getDirectoryReference("mydir").listFilesAndDirectories(); + for (ListFileItem listFileItem : listResult) { + if (listFileItem instanceof CloudFileDirectory) { + count++; + assertEquals("yourDir", ((CloudFileDirectory) listFileItem).getName()); + } + else { + count++; + assertEquals("myfile", ((CloudFile) listFileItem).getName()); + } + } + + assertEquals(2, count); + + snapshot.delete(); + } + + @Test + public void testUnsupportedApisShareSnapshot() throws StorageException, URISyntaxException { + CloudFileClient client = FileTestHelper.createCloudFileClient(); + this.share.create(); + this.share.downloadPermissions(); + CloudFileShare snapshot = this.share.createSnapshot(); + try { + snapshot.createSnapshot(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + new CloudFileShare(snapshot.getQualifiedUri(), client.getCredentials(), "2016-10-24T16:37:17.0000000Z"); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED, e.getMessage()); + } + try { + snapshot.downloadPermissions(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + snapshot.getStats(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + snapshot.uploadMetadata(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + FileSharePermissions permissions = new FileSharePermissions(); + snapshot.uploadPermissions(permissions); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + snapshot.uploadProperties(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + + snapshot.delete(); + } + private static void assertPermissionsEqual(FileSharePermissions expected, FileSharePermissions actual) { HashMap expectedPolicies = expected.getSharedAccessPolicies(); HashMap actualPolicies = actual.getSharedAccessPolicies(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java index 7261360..e85239d 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java @@ -1,11 +1,11 @@ /** * 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. @@ -15,6 +15,10 @@ package com.microsoft.azure.storage.file; import com.microsoft.azure.storage.Constants; +import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.SR; +import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.core.UriQueryBuilder; import com.microsoft.azure.storage.NameValidator; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RetryNoRetry; @@ -35,8 +39,10 @@ import com.microsoft.azure.storage.blob.CloudPageBlob; import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions; import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; -import com.microsoft.azure.storage.core.Base64; -import com.microsoft.azure.storage.core.Utility; +import com.microsoft.azure.storage.TestRunners.CloudTests; +import com.microsoft.azure.storage.TestRunners.DevFabricTests; +import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.TestRunners.SlowTests; import org.junit.After; import org.junit.Before; @@ -47,6 +53,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.HttpURLConnection; import java.net.URI; @@ -106,7 +113,7 @@ public void CloudFileNameValidation() invalidFileTestHelper("", "Between 1 and 255 characters.", "Invalid file name. The name may not be null, empty, or whitespace only."); invalidFileTestHelper(new String(new char[256]).replace("\0", "n"), "Between 1 and 255 characters.", "Invalid file name length. The name must be between 1 and 255 characters long."); } - + private void invalidFileTestHelper(String fileName, String failMessage, String exceptionMessage) { try @@ -119,10 +126,10 @@ private void invalidFileTestHelper(String fileName, String failMessage, String e assertEquals(exceptionMessage, e.getMessage()); } } - + /** * Test file creation and deletion. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -151,7 +158,7 @@ public void testCloudFileCreate() throws StorageException, URISyntaxException { /** * Test file constructor. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -175,7 +182,7 @@ public void testCloudFileConstructor() throws URISyntaxException, StorageExcepti /** * Test file resizing. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -208,7 +215,7 @@ public void testCloudFileResize() throws URISyntaxException, StorageException { /** * Test file creation with invalid sizes. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -236,7 +243,7 @@ public void testCloudFileCreateInvalidSize() throws StorageException, URISyntaxE /** * Test file deleteIfExists. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -252,7 +259,7 @@ public void testCloudFileDeleteIfExists() throws URISyntaxException, StorageExce /** * Test file exits method. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -275,7 +282,7 @@ public void testCloudFileExists() throws URISyntaxException, StorageException { /** * Test file getProperties. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -310,7 +317,7 @@ public void testCloudFileDownloadAttributes() throws URISyntaxException, Storage /** * Test file setProperties. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -364,7 +371,7 @@ public void testCloudFileSetProperties() throws StorageException, URISyntaxExcep /** * Test file creation with metadata. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -483,23 +490,23 @@ public void testCopyWithChineseChars() throws StorageException, IOException, URI @Override public void eventOccurred(SendingRequestEvent eventArg) { HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); - + try { CloudFileDirectory rootDirectory = CloudFileTests.this.share.getRootDirectoryReference(); - + // Test the copy destination request url assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", con.getURL().toString()); - + // Test the copy source request property assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", con.getRequestProperty("x-ms-copy-source")); } catch (Exception e) { fail("This code should not generate any exceptions."); - } + } } }); - + copyDestination.startCopy(copySource.getUri(), null, null, null, ctx); copyDestination.startCopy(copySource, null, null, null, ctx); } @@ -512,7 +519,7 @@ public void testCopyFileWithMetadataOverride() throws URISyntaxException, Storag String data = "String data"; CloudFile source = this.share.getRootDirectoryReference().getFileReference("source"); FileTestHelper.setFileProperties(source); - + // do this to make sure the set MD5 can be compared, otherwise when the dummy value // doesn't match the actual MD5 an exception would be thrown FileRequestOptions options = new FileRequestOptions(); @@ -558,7 +565,7 @@ public void testCopyFileWithMetadataOverride() throws URISyntaxException, Storag /** * Start copying a file and then abort - * + * * @throws StorageException * @throws URISyntaxException * @throws IOException @@ -585,7 +592,7 @@ public void testCopyFileAbort() throws StorageException, URISyntaxException, IOE /** * Test file stream uploading. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -611,7 +618,7 @@ public void testFileUploadFromStreamTest1() throws URISyntaxException, StorageEx /** * Create a file and try to download a range of its contents - * + * * @throws StorageException * @throws URISyntaxException * @throws IOException @@ -746,7 +753,7 @@ public void eventOccurred(SendingRequestEvent eventArg) { /** * Test downloading a file range. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -877,7 +884,7 @@ public void testCloudFileUploadFromStreamWithAccessCondition() throws URISyntaxE /** * Test file input stream. - * + * * @throws URISyntaxException * @throws StorageException * @throws IOException @@ -916,7 +923,7 @@ public void testCloudFileInputStream() throws URISyntaxException, StorageExcepti /** * Test file uploading from byte arrays. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -933,7 +940,7 @@ public void testCloudFileUploadFromByteArray() throws Exception { /** * Test file upload and download using text. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -949,7 +956,7 @@ public void testCloudFileUploadDownloadFromText() throws IOException, StorageExc /** * Test file upload and download using io files. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -982,7 +989,7 @@ private void doUploadFromByteArrayTest(CloudFile file, int bufferSize, int buffe } } - private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOException, StorageException { + private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOException, StorageException, URISyntaxException { File sourceFile = File.createTempFile("sourceFile", ".tmp"); File destinationFile = new File(sourceFile.getParentFile(), "destinationFile.tmp"); @@ -1019,7 +1026,7 @@ private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOExc /** * Test file range uploads. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1072,7 +1079,7 @@ public void testCloudFileUploadRange() throws URISyntaxException, StorageExcepti /** * Test clearing file ranges. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1111,7 +1118,7 @@ public void testCloudFileClearRange() throws URISyntaxException, StorageExceptio /** * Test file resizing. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1141,7 +1148,7 @@ public void testCloudFileResize2() throws StorageException, URISyntaxException { /** * Test file range downloading. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1181,7 +1188,7 @@ public void testCloudFileDownloadRange() throws StorageException, URISyntaxExcep /** * Test downloadAttributes. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1193,7 +1200,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio FileRequestOptions options = new FileRequestOptions(); options.setDisableContentMD5Validation(true); - // with explicit upload/download of properties + // with explicit upload/download of properties String fileName1 = FileTestHelper.generateRandomFileName(); CloudFile fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1); @@ -1208,7 +1215,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio FileTestHelper.assertAreEqual(props1, props2); - // by uploading/downloading the file + // by uploading/downloading the file fileName1 = FileTestHelper.generateRandomFileName(); fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1); @@ -1225,7 +1232,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio /** * Test FileOutputStream. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1265,7 +1272,7 @@ public void testCloudFileOpenOutputStream() throws URISyntaxException, StorageEx /** * Test FileOutputStream. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1292,7 +1299,7 @@ public void testCloudFileOpenOutputStreamNoArgs() throws URISyntaxException, Sto /** * Test specific deleteIfExists case. - * + * * @throws URISyntaxException * @throws StorageException * @throws IOException @@ -1337,6 +1344,8 @@ public void eventOccurred(SendingRequestEvent eventArg) { } catch (StorageException e) { fail("Delete should succeed."); + } catch (URISyntaxException e) { + fail("Delete should succeed."); } } } @@ -1346,7 +1355,7 @@ public void eventOccurred(SendingRequestEvent eventArg) { file.create(2); assertFalse(file.deleteIfExists(null, null, ctx)); } - + /** * @throws StorageException * @throws URISyntaxException @@ -1362,7 +1371,7 @@ public void testFileNamePlusEncoding() throws StorageException, URISyntaxExcepti copyFile.startCopy(originalFile); FileTestHelper.waitForCopy(copyFile); - + copyFile.downloadAttributes(); originalFile.downloadAttributes(); FileProperties prop1 = copyFile.getProperties(); @@ -1375,12 +1384,12 @@ public void testFileNamePlusEncoding() throws StorageException, URISyntaxExcepti assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); assertEquals(prop1.getContentType(), prop2.getContentType()); } - + private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception { Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); cal.setTime(new Date()); cal.add(Calendar.MINUTE, 5); - + // Source SAS must have read permissions SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ)); @@ -1390,14 +1399,14 @@ private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception // Get destination reference final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination"); - + // Start copy and wait for completion StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); Constructor blobType = source.getClass().getConstructor(URI.class); String copyId = destination.startCopy(blobType.newInstance(credentials.transformUri(source.getUri()))); FileTestHelper.waitForCopy(destination); destination.downloadAttributes(); - + // Check original file references for equality assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), @@ -1515,8 +1524,108 @@ private void doCloudFileCopy(boolean sourceIsSas, boolean destinationIsSas) assertEquals(prop1.getContentType(), prop2.getContentType()); assertEquals("value", destination.getMetadata().get("Test")); - + destination.delete(); source.delete(); } + + @Test + public void testUnsupportedFileApisWithinShareSnapshot() throws StorageException, URISyntaxException { + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFile file = snapshot.getRootDirectoryReference().getFileReference("file"); + + try { + file.create(1024); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.delete(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.uploadMetadata(); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.abortCopy(null); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.clearRange(0, 512); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.startCopy(file); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } + try { + file.upload(null, 512); + fail("Shouldn't get here"); + } + catch (IllegalArgumentException e) { + assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); + } catch (IOException e) { + fail("Shouldn't get here"); + } + + snapshot.delete(); + } + + @Test + public void testSupportedFileApisInShareSnapshot() throws StorageException, URISyntaxException, UnsupportedEncodingException { + CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); + dir.deleteIfExists(); + dir.create(); + CloudFile file = dir.getFileReference("file"); + file.create(1024); + + HashMap meta = new HashMap(); + meta.put("key1", "value1"); + file.setMetadata(meta); + file.uploadMetadata(); + + CloudFileShare snapshot = this.share.createSnapshot(); + CloudFile snapshotFile = snapshot.getRootDirectoryReference() + .getDirectoryReference("dir1").getFileReference("file"); + + HashMap meta2 = new HashMap(); + meta2.put("key2", "value2"); + file.setMetadata(meta2); + file.uploadMetadata(); + snapshotFile.downloadAttributes(); + + assertTrue(snapshotFile.getMetadata().size() == 1 && snapshotFile.getMetadata().get("key1").equals("value1")); + assertNotNull(snapshotFile.getProperties().getEtag()); + + file.downloadAttributes(); + assertTrue(file.getMetadata().size() == 1 && file.getMetadata().get("key2").equals("value2")); + assertNotNull(file.getProperties().getEtag()); + assertNotEquals(file.getProperties().getEtag(), snapshotFile.getProperties().getEtag()); + + final UriQueryBuilder uriBuilder = new UriQueryBuilder(); + uriBuilder.add("sharesnapshot", snapshot.snapshotID); + CloudFile snapshotFile2 = new CloudFile(uriBuilder.addToURI(file.getUri()), this.share.getServiceClient().getCredentials()); + assertEquals(snapshot.snapshotID, snapshotFile2.getShare().snapshotID); + assertTrue(snapshotFile2.exists()); + + snapshot.delete(); + } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java index df469d7..44feb8f 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java @@ -96,7 +96,7 @@ static StorageUri ensureTrailingSlash(StorageUri uri) throws URISyntaxException } protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSize, int bufferOffset) - throws StorageException, IOException { + throws StorageException, IOException, URISyntaxException { final Random randGenerator = new Random(); final byte[] buffer = new byte[fileSize]; randGenerator.nextBytes(buffer); @@ -118,7 +118,7 @@ protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSiz } protected static void doDownloadRangeToByteArrayTest(CloudFile file, int fileSize, int bufferSize, - int bufferOffset, Long fileOffset, Long length) throws IOException, StorageException { + int bufferOffset, Long fileOffset, Long length) throws IOException, StorageException, URISyntaxException { final Random randGenerator = new Random(); final byte[] buffer = new byte[fileSize]; randGenerator.nextBytes(buffer); @@ -152,7 +152,7 @@ protected static void doDownloadRangeToByteArrayTest(CloudFile file, int fileSiz } } - protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) throws StorageException, IOException { + protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) throws StorageException, IOException, URISyntaxException { int fileLength = 1024; int resultBufSize = 1024; final Random randGenerator = new Random(); 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 655afc4..f82d3a3 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -439,7 +439,12 @@ public static class HeaderConstants { * The blob sequence number equal condition header. */ public static final String IF_SEQUENCE_NUMBER_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-eq"; - + + /** + * Specifies snapshots are to be included. + */ + public static final String INCLUDE_SNAPSHOTS_VALUE = "include"; + /** * The header that specifies the lease action to perform */ @@ -575,7 +580,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2016-05-31"; + public static final String TARGET_STORAGE_VERSION = "2016-10-16"; /** * The header that specifies the next visible time for a queue message. @@ -747,6 +752,11 @@ public static class QueryConstants { */ public static final String SNAPSHOT = "snapshot"; + /** + * The query component for snapshot time. + */ + public static final String SHARE_SNAPSHOT = "sharesnapshot"; + /** * The query component for the SAS start partition key. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java index 214ec54..e30fe93 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java @@ -564,6 +564,11 @@ public final class StorageErrorCodeStrings { */ public static final String SHARE_DISABLED = "ShareDisabled"; + /** + * The specified share contains snapshots. + */ + public static final String SHARE_HAS_SNAPSHOTS = "ShareHasSnapshots"; + /** * The specified share was not found. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index 2e33407..f2d7fd8 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -156,11 +156,6 @@ final class BlobConstants { */ public static final int DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES = 32 * Constants.MB; - /** - * Specifies snapshots are to be included. - */ - public static final String INCLUDE_SNAPSHOTS_VALUE = "include"; - /** * XML element for the latest. */ 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 904bfbd..cfb8244 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 @@ -373,7 +373,7 @@ public static HttpURLConnection deleteBlob(final URI uri, final BlobRequestOptio break; case INCLUDE_SNAPSHOTS: request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, - BlobConstants.INCLUDE_SNAPSHOTS_VALUE); + HeaderConstants.INCLUDE_SNAPSHOTS_VALUE); break; case DELETE_SNAPSHOTS_ONLY: request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java index bf4c0a0..395d8c9 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java @@ -91,6 +91,7 @@ public class SR { public static final String INVALID_MIME_RESPONSE = "Invalid MIME response received."; public static final String INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER = "Page data must be a multiple of 512 bytes. Buffer currently contains %d bytes."; public static final String INVALID_OPERATION_FOR_A_SNAPSHOT = "Cannot perform this operation on a blob representing a snapshot."; + public static final String INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT = "Cannot perform this operation on a share representing a snapshot."; public static final String INVALID_PAGE_BLOB_LENGTH = "Page blob length must be multiple of 512."; public static final String INVALID_PAGE_START_OFFSET = "Page start offset must be multiple of 512."; public static final String INVALID_RANGE_CONTENT_MD5_HEADER = "Cannot specify x-ms-range-get-content-md5 header on ranges larger than 4 MB. Either use a BlobReadStream via openRead, or disable TransactionalMD5 via the BlobRequestOptions."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java index 1fd78ad..dc3167d 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java @@ -418,7 +418,7 @@ public static StorageCredentialsSharedAccessSignature parseQuery(final StorageUr */ public static StorageCredentialsSharedAccessSignature parseQuery(final HashMap queryParams) throws StorageException { - + boolean sasParameterFound = false; List removeList = new ArrayList(); for (final Entry entry : queryParams.entrySet()) { @@ -434,9 +434,11 @@ public static StorageCredentialsSharedAccessSignature parseQuery(final HashMapCloudFile class using the specified absolute URI * and credentials. @@ -154,8 +154,9 @@ public CloudFile(final StorageUri fileAbsoluteUri) throws StorageException, URIS * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ - public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { + public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials) throws StorageException, URISyntaxException { this(new StorageUri(fileAbsoluteUri), credentials); } @@ -170,8 +171,9 @@ public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ - public CloudFile(final StorageUri fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { + public CloudFile(final StorageUri fileAbsoluteUri, final StorageCredentials credentials) throws StorageException, URISyntaxException { this.parseQueryAndVerify(fileAbsoluteUri, credentials); } @@ -229,9 +231,10 @@ protected CloudFile(final StorageUri uri, final String fileName, final CloudFile * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void abortCopy(final String copyId) throws StorageException { + public final void abortCopy(final String copyId) throws StorageException, URISyntaxException { this.abortCopy(copyId, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -253,14 +256,17 @@ public final void abortCopy(final String copyId) throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public final void abortCopy(final String copyId, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -426,9 +432,10 @@ public final String startCopy(final CloudFile sourceFile, final AccessCondition * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final String startCopy(final URI source) throws StorageException { + public final String startCopy(final URI source) throws StorageException, URISyntaxException { return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */, null /* options */, null /* opContext */); } @@ -456,16 +463,19 @@ public final String startCopy(final URI source) throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException * */ @DoesServiceRequest public final String startCopy(final URI source, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException { + throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -534,9 +544,10 @@ public String preProcessResponse(CloudFile file, CloudFileClient client, Operati * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void clearRange(final long offset, final long length) throws StorageException { + public void clearRange(final long offset, final long length) throws StorageException, URISyntaxException { this.clearRange(offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -563,14 +574,17 @@ public void clearRange(final long offset, final long length) throws StorageExcep * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void clearRange(final long offset, final long length, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException { + FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); @@ -587,9 +601,10 @@ public void clearRange(final long offset, final long length, final AccessConditi * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void create(final long size) throws StorageException { + public void create(final long size) throws StorageException, URISyntaxException { this.create(size, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -612,15 +627,18 @@ public void create(final long size) throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void create(final long size, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.createImpl(size, accessCondition, options), @@ -672,9 +690,10 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void delete() throws StorageException { + public final void delete() throws StorageException, URISyntaxException { this.delete(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -694,14 +713,17 @@ public final void delete() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public final void delete(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -716,10 +738,11 @@ public final void delete(final AccessCondition accessCondition, FileRequestOptio * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException * */ @DoesServiceRequest - public final boolean deleteIfExists() throws StorageException { + public final boolean deleteIfExists() throws StorageException, URISyntaxException { return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -741,11 +764,13 @@ public final boolean deleteIfExists() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public final boolean deleteIfExists(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + this.getShare().assertNoSnapshot(); boolean exists = this.exists(true, accessCondition, options, opContext); if (exists) { @@ -1264,7 +1289,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { return FileRequest.getFileRanges(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition); + options, context, accessCondition, file.getShare().snapshotID); } @Override @@ -1331,7 +1356,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op // : accessCondition; return FileRequest.getFile(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, this.getOffset(), this.getLength(), + options, context, accessCondition, file.getShare().snapshotID, this.getOffset(), this.getLength(), (options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated())); } @@ -1506,7 +1531,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op throws Exception { return FileRequest.getFileProperties( file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition); + options, context, accessCondition, file.getShare().snapshotID); } @Override @@ -1603,7 +1628,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op throws Exception { return FileRequest.getFileProperties( file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition); + options, context, accessCondition, file.getShare().snapshotID); } @Override @@ -1825,9 +1850,10 @@ public final FileInputStream openRead(final AccessCondition accessCondition, Fil * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public FileOutputStream openWriteExisting() throws StorageException { + public FileOutputStream openWriteExisting() throws StorageException, URISyntaxException { return this .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -1851,10 +1877,11 @@ public FileOutputStream openWriteExisting() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { return this .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -1874,9 +1901,10 @@ public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileR * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public FileOutputStream openWriteNew(final long length) throws StorageException { + public FileOutputStream openWriteNew(final long length) throws StorageException, URISyntaxException { return this .openOutputStreamInternal(length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -1906,10 +1934,11 @@ public FileOutputStream openWriteNew(final long length) throws StorageException * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public FileOutputStream openWriteNew(final long length, AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException { + FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { return openOutputStreamInternal(length, accessCondition, options, opContext); } @@ -1936,13 +1965,16 @@ public FileOutputStream openWriteNew(final long length, AccessCondition accessCo * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException { + FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient, false /* setStartTime */); if (length != null) { @@ -1977,9 +2009,10 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ public void uploadFromByteArray(final byte[] buffer, final int offset, final int length) throws StorageException, - IOException { + IOException, URISyntaxException { uploadFromByteArray(buffer, offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2006,10 +2039,11 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ public void uploadFromByteArray(final byte[] buffer, final int offset, final int length, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, IOException { + throws StorageException, IOException, URISyntaxException { ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length); this.upload(inputStream, length, accessCondition, options, opContext); inputStream.close(); @@ -2024,8 +2058,9 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ - public void uploadFromFile(final String path) throws StorageException, IOException { + public void uploadFromFile(final String path) throws StorageException, IOException, URISyntaxException { uploadFromFile(path, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2048,9 +2083,10 @@ public void uploadFromFile(final String path) throws StorageException, IOExcepti * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ public void uploadFromFile(final String path, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, IOException { + OperationContext opContext) throws StorageException, IOException, URISyntaxException { File file = new File(path); long fileLength = file.length(); InputStream inputStream = new BufferedInputStream(new java.io.FileInputStream(file)); @@ -2068,8 +2104,9 @@ public void uploadFromFile(final String path, final AccessCondition accessCondit * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ - public void uploadText(final String content) throws StorageException, IOException { + public void uploadText(final String content) throws StorageException, IOException, URISyntaxException { this.uploadText(content, null /* charsetName */, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2096,9 +2133,10 @@ public void uploadText(final String content) throws StorageException, IOExceptio * @throws StorageException * If a storage service error occurred. * @throws IOException + * @throws URISyntaxException */ public void uploadText(final String content, final String charsetName, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, IOException { + FileRequestOptions options, OperationContext opContext) throws StorageException, IOException, URISyntaxException { byte[] bytes = (charsetName == null) ? content.getBytes() : content.getBytes(charsetName); this.uploadFromByteArray(bytes, 0, bytes.length, accessCondition, options, opContext); } @@ -2118,10 +2156,11 @@ public void uploadText(final String content, final String charsetName, final Acc * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void uploadRange(final InputStream sourceStream, final long offset, final long length) - throws StorageException, IOException { + throws StorageException, IOException, URISyntaxException { this.uploadRange(sourceStream, offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2150,15 +2189,18 @@ public void uploadRange(final InputStream sourceStream, final long offset, final * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void uploadRange(final InputStream sourceStream, final long offset, final long length, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, IOException { + throws StorageException, IOException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); @@ -2286,9 +2328,10 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void uploadMetadata() throws StorageException { + public final void uploadMetadata() throws StorageException, URISyntaxException { this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2312,15 +2355,18 @@ public final void uploadMetadata() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public final void uploadMetadata(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2376,9 +2422,10 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void uploadProperties() throws StorageException { + public final void uploadProperties() throws StorageException, URISyntaxException { this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */); } @@ -2401,14 +2448,17 @@ public final void uploadProperties() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public final void uploadProperties(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2460,8 +2510,9 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ - public void resize(long size) throws StorageException { + public void resize(long size) throws StorageException, URISyntaxException { this.resize(size, null /* accessCondition */, null /* options */, null /* operationContext */); } @@ -2483,13 +2534,16 @@ public void resize(long size) throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ public void resize(long size, AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2544,9 +2598,10 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException { + public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException, URISyntaxException { this.upload(sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2574,14 +2629,17 @@ public void upload(final InputStream sourceStream, final long length) throws Sto * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void upload(final InputStream sourceStream, final long length, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, IOException { + FileRequestOptions options, OperationContext opContext) throws StorageException, IOException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); if (length <= 0) { @@ -2670,9 +2728,10 @@ protected static String getParentNameFromURI(final StorageUri resourceAddress, f * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) - throws StorageException { + throws StorageException, URISyntaxException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { @@ -2680,7 +2739,7 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -2697,6 +2756,13 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } + + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + + final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); + if (snapshotIDs != null && snapshotIDs.length > 0) { + this.getShare().snapshotID = snapshotIDs[0]; + } } protected void updateEtagAndLastModifiedFromResponse(HttpURLConnection request) { @@ -2733,7 +2799,8 @@ public final CloudFileShare getShare() throws StorageException, URISyntaxExcepti if (this.share == null) { final StorageUri shareUri = PathUtility.getShareURI(this.getStorageUri(), this.fileServiceClient.isUsePathStyleUris()); - this.share = new CloudFileShare(shareUri, this.fileServiceClient.getCredentials()); + + this.share = new CloudFileShare(shareUri, this.fileServiceClient.getCredentials(), null); } return this.share; 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 f5bac52..030fa41 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 @@ -17,6 +17,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.util.EnumSet; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; @@ -100,7 +101,29 @@ public CloudFileClient(StorageUri storageUri, StorageCredentials credentials) { */ public CloudFileShare getShareReference(final String shareName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("shareName", shareName); - return new CloudFileShare(shareName, this); + return this.getShareReference(shareName, null); + } + + /** + * Gets a {@link CloudFileShare} object with the specified name. + * + * @param shareName + * The name of the share, which must adhere to share naming rules. The share name should not + * include any path separator characters (/). + * Share names must be lowercase, between 3-63 characters long and must start with a letter or + * number. Share names may contain only letters, numbers, and the dash (-) character. + * @param snapshotID + * A String that represents the snapshot ID of the share. + * @return A reference to a {@link CloudFileShare} object. + * @throws StorageException + * @throws URISyntaxException + * + * @see Naming and Referencing Shares, + * Directories, Files, and Metadata + */ + public CloudFileShare getShareReference(final String shareName, String snapshotID) throws URISyntaxException, StorageException { + Utility.assertNotNullOrEmpty("shareName", shareName); + return new CloudFileShare(shareName, snapshotID, this); } /** @@ -111,7 +134,7 @@ public CloudFileShare getShareReference(final String shareName) throws URISyntax */ @DoesServiceRequest public Iterable listShares() { - return this.listSharesWithPrefix(null, ShareListingDetails.NONE, null /* options */, null /* opContext */); + return this.listSharesWithPrefix(null, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */); } /** @@ -126,7 +149,7 @@ public Iterable listShares() { */ @DoesServiceRequest public Iterable listShares(final String prefix) { - return this.listSharesWithPrefix(prefix, ShareListingDetails.NONE, null /* options */, null /* opContext */); + return this.listSharesWithPrefix(prefix, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */); } /** @@ -136,7 +159,8 @@ public Iterable listShares(final String prefix) { * @param prefix * A String that represents the share name prefix. * @param detailsIncluded - * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. + * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate + * whether share snapshots and/or metadata will be returned. * @param options * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying * null will use the default request options from the associated service client ( @@ -150,7 +174,7 @@ public Iterable listShares(final String prefix) { * shares for this client. */ @DoesServiceRequest - public Iterable listShares(final String prefix, final ShareListingDetails detailsIncluded, + public Iterable listShares(final String prefix, final EnumSet detailsIncluded, final FileRequestOptions options, final OperationContext opContext) { return this.listSharesWithPrefix(prefix, detailsIncluded, options, opContext); } @@ -166,7 +190,7 @@ public Iterable listShares(final String prefix, final ShareListi */ @DoesServiceRequest public ResultSegment listSharesSegmented() throws StorageException { - return this.listSharesSegmented(null, ShareListingDetails.NONE, null, null /* continuationToken */, + return this.listSharesSegmented(null, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -186,7 +210,7 @@ public ResultSegment listSharesSegmented() throws StorageExcepti */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix) throws StorageException { - return this.listSharesWithPrefixSegmented(prefix, ShareListingDetails.NONE, null, null /* continuationToken */, + return this.listSharesWithPrefixSegmented(prefix, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -197,7 +221,8 @@ public ResultSegment listSharesSegmented(final String prefix) th * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. + * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate + * whether share snapshots and/or metadata will be returned. * @param maxResults * The maximum number of results to retrieve. If null or greater * than 5000, the server will return up to 5,000 items. Must be at least 1. @@ -221,7 +246,7 @@ public ResultSegment listSharesSegmented(final String prefix) th */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix, - final ShareListingDetails detailsIncluded, final Integer maxResults, + final EnumSet detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, final FileRequestOptions options, final OperationContext opContext) throws StorageException { return this.listSharesWithPrefixSegmented(prefix, detailsIncluded, maxResults, continuationToken, options, @@ -235,7 +260,8 @@ public ResultSegment listSharesSegmented(final String prefix, * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. + * A java.util.EnumSet object that contains {@link ShareListingDetails} + * values that indicate whether snapshots and/or metadata are returned. * @param options * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying * null will use the default request options from the associated service client ( @@ -249,7 +275,7 @@ public ResultSegment listSharesSegmented(final String prefix, * shares whose names begin with the specified prefix. */ private Iterable listSharesWithPrefix(final String prefix, - final ShareListingDetails detailsIncluded, FileRequestOptions options, OperationContext opContext) { + final EnumSet detailsIncluded, FileRequestOptions options, OperationContext opContext) { if (opContext == null) { opContext = new OperationContext(); } @@ -271,7 +297,8 @@ private Iterable listSharesWithPrefix(final String prefix, * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A {@link FileListingDetails} value that indicates whether share metadata will be returned. + * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate + * whether share snapshots and/or metadata will be returned. * @param maxResults * The maximum number of results to retrieve. If null or greater * than 5000, the server will return up to 5,000 items. Must be at least 1. @@ -294,7 +321,7 @@ private Iterable listSharesWithPrefix(final String prefix, * If a storage service error occurred. */ private ResultSegment listSharesWithPrefixSegmented(final String prefix, - final ShareListingDetails detailsIncluded, final Integer maxResults, + final EnumSet detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -315,7 +342,7 @@ private ResultSegment listSharesWithPrefixSegmented(final String } private StorageRequest> listSharesWithPrefixSegmentedImpl( - final String prefix, final ShareListingDetails detailsIncluded, final Integer maxResults, + final String prefix, final EnumSet detailsIncluded, final Integer maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.SHARE); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java index a479446..92735f7 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java @@ -94,8 +94,9 @@ public final class CloudFileDirectory implements ListFileItem { * @param directoryAbsoluteUri * A {@link URI} that represents the file directory's address. * @throws StorageException + * @throws URISyntaxException */ - public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException { + public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException, URISyntaxException { this(new StorageUri(directoryAbsoluteUri)); } @@ -105,8 +106,9 @@ public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageExceptio * @param directoryAbsoluteUri * A {@link StorageUri} that represents the file directory's address. * @throws StorageException + * @throws URISyntaxException */ - public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException { + public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException, URISyntaxException { this(directoryAbsoluteUri, (StorageCredentials) null); } @@ -119,9 +121,10 @@ public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageE * @param credentials * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException + * @throws URISyntaxException */ public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentials credentials) - throws StorageException { + throws StorageException, URISyntaxException { this(new StorageUri(directoryAbsoluteUri), credentials); } @@ -134,9 +137,10 @@ public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentia * @param credentials * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException + * @throws URISyntaxException */ public CloudFileDirectory(final StorageUri directoryAbsoluteUri, final StorageCredentials credentials) - throws StorageException { + throws StorageException, URISyntaxException { this.parseQueryAndVerify(directoryAbsoluteUri, credentials); } @@ -167,9 +171,10 @@ protected CloudFileDirectory(final StorageUri uri, final String directoryName, f * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void create() throws StorageException { + public void create() throws StorageException, URISyntaxException { this.create(null /* options */, null /* opContext */); } @@ -187,13 +192,16 @@ public void create() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void create(FileRequestOptions options, OperationContext opContext) throws StorageException { + public void create(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -250,9 +258,10 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public boolean createIfNotExists() throws StorageException { + public boolean createIfNotExists() throws StorageException, URISyntaxException { return this.createIfNotExists(null /* options */, null /* opContext */); } @@ -272,11 +281,14 @@ public boolean createIfNotExists() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException { + public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + this.getShare().assertNoSnapshot(); + boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext); if (exists) { return false; @@ -303,9 +315,10 @@ public boolean createIfNotExists(FileRequestOptions options, OperationContext op * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void delete() throws StorageException { + public void delete() throws StorageException, URISyntaxException { this.delete(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -325,14 +338,17 @@ public void delete() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException { + throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -378,9 +394,10 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public boolean deleteIfExists() throws StorageException { + public boolean deleteIfExists() throws StorageException, URISyntaxException { return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -402,10 +419,11 @@ public boolean deleteIfExists() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); @@ -498,7 +516,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory OperationContext context) throws Exception { return FileRequest.getDirectoryProperties( directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, - accessCondition); + accessCondition, directory.getShare().snapshotID); } @Override @@ -511,7 +529,12 @@ public void signRequest(HttpURLConnection connection, CloudFileClient client, Op public Boolean preProcessResponse(CloudFileDirectory directory, CloudFileClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { - directory.updatePropertiesFromResponse(this.getConnection()); + // Set properties + final FileDirectoryAttributes attributes = + FileResponse.getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris()); + directory.setMetadata(attributes.getMetadata()); + directory.setProperties(attributes.getProperties()); + return Boolean.valueOf(true); } else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { @@ -546,9 +569,10 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public void uploadMetadata() throws StorageException { + public void uploadMetadata() throws StorageException, URISyntaxException { this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -568,14 +592,17 @@ public void uploadMetadata() throws StorageException { * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException { + throws StorageException, URISyntaxException { if (opContext == null) { opContext = new OperationContext(); } + this.getShare().assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -679,7 +706,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory OperationContext context) throws Exception { return FileRequest.getDirectoryProperties( directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, - accessCondition); + accessCondition, directory.getShare().snapshotID); } @Override @@ -900,7 +927,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory .getNextMarker() : null); return FileRequest.listFilesAndDirectories( directory.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, listingContext); + options, context, listingContext, directory.getShare().snapshotID); } @Override @@ -1058,7 +1085,9 @@ public CloudFileDirectory getParent() throws URISyntaxException, StorageExceptio if (parentName != null) { StorageUri parentURI = PathUtility.appendPathToUri(this.getShare().getStorageUri(), parentName); + this.parent = new CloudFileDirectory(parentURI, this.getServiceClient().getCredentials()); + //this.parent = new CloudFileDirectory(parentURI, parentName, this.getShare()); } } return this.parent; @@ -1135,9 +1164,10 @@ protected void setStorageUri(final StorageUri storageUri) { * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) - throws StorageException { + throws StorageException, URISyntaxException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { @@ -1145,7 +1175,7 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -1162,6 +1192,13 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } + + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + + final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); + if (snapshotIDs != null && snapshotIDs.length > 0) { + this.getShare().snapshotID = snapshotIDs[0]; + } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java index 318b949..3f1267b 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java @@ -76,6 +76,11 @@ public final class CloudFileShare { */ private StorageUri storageUri; + /** + * Holds the snapshot ID. + */ + String snapshotID; + /** * Holds a reference to the associated service client. */ @@ -90,6 +95,8 @@ public final class CloudFileShare { * The share name should not include any path separator characters (/). * Share names must be lowercase, between 3-63 characters long and must start with a letter or * number. Share names may contain only letters, numbers, and the dash (-) character. + * @param snapshotID + * A String that represents the snapshot version, if applicable. * @param client * A {@link CloudFileClient} object that represents the associated service client, and that specifies the * endpoint for the File service. @@ -102,13 +109,14 @@ public final class CloudFileShare { * @see Naming and Referencing Shares, * Directories, Files, and Metadata */ - protected CloudFileShare(final String shareName, final CloudFileClient client) throws URISyntaxException, + protected CloudFileShare(final String shareName, String snapshotID, final CloudFileClient client) throws URISyntaxException, StorageException { Utility.assertNotNull("client", client); Utility.assertNotNull("shareName", shareName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), shareName); this.name = shareName; + this.snapshotID = snapshotID; this.fileServiceClient = client; } @@ -135,9 +143,9 @@ public CloudFileShare(final URI uri) throws StorageException { * If a storage service error occurred. */ public CloudFileShare(final StorageUri storageUri) throws StorageException { - this(storageUri, (StorageCredentials) null); + this(storageUri, (StorageCredentials) null, null); } - + /** * Creates an instance of the CloudFileShare class using the specified URI and credentials. * @@ -145,12 +153,14 @@ public CloudFileShare(final StorageUri storageUri) throws StorageException { * A java.net.URI object that represents the absolute URI of the share. * @param credentials * A {@link StorageCredentials} object used to authenticate access. + * @param snapshotID + * A String that represents the snapshot version, if applicable. * * @throws StorageException * If a storage service error occurred. */ - public CloudFileShare(final URI uri, final StorageCredentials credentials) throws StorageException { - this(new StorageUri(uri), credentials); + public CloudFileShare(final URI uri, final StorageCredentials credentials, String snapshotID) throws StorageException { + this(new StorageUri(uri), credentials, snapshotID); } /** @@ -160,12 +170,22 @@ public CloudFileShare(final URI uri, final StorageCredentials credentials) throw * A {@link StorageUri} object which represents the absolute StorageUri of the share. * @param credentials * A {@link StorageCredentials} object used to authenticate access. - * + * @param snapshotID + * A String that represents the snapshot version, if applicable. * @throws StorageException * If a storage service error occurred. */ - public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials) throws StorageException { + public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials, String snapshotID) throws StorageException { this.parseQueryAndVerify(storageUri, credentials); + + if (snapshotID != null) { + if (this.snapshotID != null) { + throw new IllegalArgumentException(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED); + } + else { + this.snapshotID = snapshotID; + } + } } /** @@ -200,6 +220,11 @@ public void create(FileRequestOptions options, OperationContext opContext) throw opContext = new OperationContext(); } + assertNoSnapshot(); + if (this.properties != null && this.properties.getShareQuota() != null) { + Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA); + } + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -317,7 +342,7 @@ public boolean createIfNotExists(FileRequestOptions options, OperationContext op */ @DoesServiceRequest public void delete() throws StorageException { - this.delete(null /* accessCondition */, null /* options */, null /* opContext */); + this.delete(DeleteShareSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */); } /** @@ -340,6 +365,36 @@ public void delete() throws StorageException { @DoesServiceRequest public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { + this.delete(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext); + } + + /** + * Deletes the share using the specified snapshot and request options, and operation context. + *

+ * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use + * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value + * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share. + * + * @param deleteSnapshotsOption + * A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share + * and its snapshots. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -347,12 +402,12 @@ public void delete(AccessCondition accessCondition, FileRequestOptions options, opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(accessCondition, options), + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(deleteSnapshotsOption, accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest deleteImpl( - final AccessCondition accessCondition, final FileRequestOptions options) { + final DeleteShareSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition, final FileRequestOptions options) { final StorageRequest putRequest = new StorageRequest(options, this.getStorageUri()) { @@ -361,7 +416,7 @@ private StorageRequest deleteImpl( public HttpURLConnection buildRequest( CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.deleteShare( - share.getTransformedAddress().getPrimaryUri(), options, context, accessCondition); + share.getTransformedAddress().getPrimaryUri(), options, context, accessCondition, share.snapshotID, deleteSnapshotsOption); } @Override @@ -394,12 +449,13 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope */ @DoesServiceRequest public boolean deleteIfExists() throws StorageException { - return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); + return this.deleteIfExists(DeleteShareSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */); } - + /** * Deletes the share if it exists using the specified request options and operation context. * + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. * @param options @@ -419,12 +475,45 @@ public boolean deleteIfExists() throws StorageException { @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { + return this.deleteIfExists(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext); + + } + + /** + * Deletes the share if it exists, using the specified snapshot and request options, and operation context. + *

+ * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use + * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value + * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share. + * + * @param deleteSnapshotsOption + * A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share + * and its snapshots. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return true if the share existed and was deleted; otherwise, false. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public boolean deleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, + OperationContext opContext) throws StorageException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); if (exists) { try { - this.delete(accessCondition, options, opContext); + this.delete(deleteSnapshotsOption, accessCondition, options, opContext); return true; } catch (StorageException e) { @@ -499,7 +588,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, accessCondition); + options, context, accessCondition, share.snapshotID); } @Override @@ -567,6 +656,8 @@ public FileSharePermissions downloadPermissions(AccessCondition accessCondition, opContext = new OperationContext(); } + assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -626,6 +717,136 @@ public FileSharePermissions postProcessResponse(HttpURLConnection connection, return getRequest; } + /** + * Creates a snapshot of the share. + * + * @return A CloudFileShare object that represents the snapshot of the share. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final CloudFileShare createSnapshot() throws StorageException { + return this + .createSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Creates a snapshot of the file share using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A CloudFileShare object that represents the snapshot of the file share. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final CloudFileShare createSnapshot(final AccessCondition accessCondition, FileRequestOptions options, + OperationContext opContext) throws StorageException { + return this.createSnapshot(null /* metadata */, accessCondition, options, opContext); + } + + /** + * Creates a snapshot of the file share using the specified request options and operation context. + * + * @param metadata + * A collection of name-value pairs defining the metadata of the snapshot, or null. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the file share. + * @param options + * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudFileClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A CloudFileShare object that represents the snapshot of the file share. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final CloudFileShare createSnapshot(final HashMap metadata, + final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) + throws StorageException { + assertNoSnapshot(); + + if (opContext == null) { + opContext = new OperationContext(); + } + + opContext.initialize(); + options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); + + return ExecutionEngine + .executeWithRetry(this.fileServiceClient, this, + this.createSnapshotImpl(metadata, accessCondition, options), options.getRetryPolicyFactory(), + opContext); + } + + private StorageRequest createSnapshotImpl( + final HashMap metadata, final AccessCondition accessCondition, + final FileRequestOptions options) { + final StorageRequest putRequest = + new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) + throws Exception { + return FileRequest.snapshotShare(share.getTransformedAddress().getUri(this.getCurrentLocation()), + options, context, accessCondition); + } + + @Override + public void setHeaders(HttpURLConnection connection, CloudFileShare share, OperationContext context) { + if (metadata != null) { + FileRequest.addMetadata(connection, metadata, context); + } + } + + @Override + public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public CloudFileShare preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + final String snapshotTime = FileResponse.getSnapshotTime(this.getConnection()); + CloudFileShare snapshot = new CloudFileShare(share.getName(), snapshotTime, client); + snapshot.setProperties(new FileShareProperties(share.properties)); + + // use the specified metadata if not null : otherwise share's metadata + snapshot.setMetadata(metadata != null ? metadata : share.metadata); + + share.updatePropertiesFromResponse(this.getConnection()); + + return snapshot; + } + }; + + return putRequest; + } + /** * Queries the service for this share's {@link ShareStats}. * @@ -662,6 +883,8 @@ public ShareStats getStats(FileRequestOptions options, OperationContext opContex opContext = new OperationContext(); } + assertNoSnapshot(); + opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -781,7 +1004,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, accessCondition); + options, context, accessCondition, share.snapshotID); } @Override @@ -794,7 +1017,11 @@ public void signRequest(HttpURLConnection connection, CloudFileClient client, Op public Boolean preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { - share.updatePropertiesFromResponse(this.getConnection()); + final FileShareAttributes attributes = FileResponse.getFileShareAttributes(this.getConnection(), + client.isUsePathStyleUris()); + share.metadata = attributes.getMetadata(); + share.properties = attributes.getProperties(); + return Boolean.valueOf(true); } else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { @@ -802,6 +1029,7 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { } else { this.setNonExceptionedRetryableFailure(true); + // return false instead of null to avoid SCA issues return false; } @@ -812,6 +1040,10 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { } private void updatePropertiesFromResponse(HttpURLConnection request) { + if (this.getProperties() == null) { + this.properties = new FileShareProperties(); + } + // ETag this.getProperties().setEtag(request.getHeaderField(Constants.HeaderConstants.ETAG)); @@ -824,6 +1056,15 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { } } + /** + * Asserts that the share is not a snapshot. + */ + protected void assertNoSnapshot() { + if (isSnapshot()) { + throw new IllegalArgumentException(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT); + } + } + /** * Returns a shared access signature for the share. Note this does not contain the leading "?". * @@ -931,6 +1172,8 @@ public void uploadMetadata() throws StorageException { @DoesServiceRequest public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { + assertNoSnapshot(); + if (opContext == null) { opContext = new OperationContext(); } @@ -1019,6 +1262,12 @@ public final void uploadProperties() throws StorageException { public final void uploadProperties( AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { + assertNoSnapshot(); + + if (this.properties != null && this.properties.getShareQuota() != null) { + Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA); + } + if (opContext == null) { opContext = new OperationContext(); } @@ -1100,6 +1349,8 @@ public void uploadPermissions(final FileSharePermissions permissions) throws Sto @DoesServiceRequest public void uploadPermissions(final FileSharePermissions permissions, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { + assertNoSnapshot(); + if (opContext == null) { opContext = new OperationContext(); } @@ -1200,7 +1451,14 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + + final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); + if (snapshotIDs != null && snapshotIDs.length > 0) { + this.snapshotID = snapshotIDs[0]; + } + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -1237,6 +1495,17 @@ public URI getUri() { return this.storageUri.getPrimaryUri(); } + /** + * Indicates whether this share is a snapshot. + * + * @return true if the share is a snapshot, otherwise false. + * + * @see DeleteSnapshotsOption + */ + public final boolean isSnapshot() { + return this.snapshotID != null; + } + /** * Returns the list of URIs for all locations. * @@ -1246,6 +1515,24 @@ public StorageUri getStorageUri() { return this.storageUri; } + /** + * Returns the snapshot or shared access signature qualified URI for this share. + * + * @return A java.net.URI object that represents the snapshot or shared access signature. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public final URI getQualifiedUri() throws URISyntaxException, StorageException { + if (this.isSnapshot()) { + return PathUtility.addToQuery(this.getUri(), String.format("sharesnapshot=%s", this.snapshotID)); + } + + return this.fileServiceClient.getCredentials().transformUri(this.getUri()); + } + /** * Returns the name of the share. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java new file mode 100644 index 0000000..c44b495 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java @@ -0,0 +1,35 @@ +/** + * Copyright Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package com.microsoft.azure.storage.file; + +/** + * Specifies options when calling the delete share operation. + */ +public enum DeleteShareSnapshotsOption { + + /** + * Specifies deleting the blob and its snapshots. + */ + INCLUDE_SNAPSHOTS, + + /** + * Specifies deleting the blob only. If the blob has snapshots, this option will result in an error from the + * service. + */ + NONE +} 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 db6b41e..7ab437c 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 @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URISyntaxException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.Callable; @@ -205,6 +206,8 @@ public void close() throws IOException { } catch (final StorageException e) { throw Utility.initIOException(e); + } catch (URISyntaxException e) { + throw Utility.initIOException(e); } } finally { @@ -227,9 +230,10 @@ public void close() throws IOException { * * @throws StorageException * An exception representing any error which occurred during the operation. + * @throws URISyntaxException */ @DoesServiceRequest - private void commit() throws StorageException { + private void commit() throws StorageException, URISyntaxException { if (this.options.getStoreFileContentMD5()) { this.parentFileRef.getProperties().setContentMD5(Base64.encode(this.md5Digest.digest())); } @@ -271,7 +275,7 @@ private synchronized void dispatchWrite(final int writeLength) throws IOExceptio worker = new Callable() { @Override - public Void call() { + public Void call() throws URISyntaxException { try { fileRef.uploadRange(bufferRef, opOffset, opWriteLength, FileOutputStream.this.accessCondition, FileOutputStream.this.options, FileOutputStream.this.opContext); 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 c119ce8..5489208 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java @@ -18,6 +18,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.util.EnumSet; import java.util.Map; import com.microsoft.azure.storage.AccessCondition; @@ -38,6 +39,8 @@ final class FileRequest { private static final String RANGE_LIST_QUERY_ELEMENT_NAME = "rangelist"; + private static final String SNAPSHOTS_QUERY_ELEMENT_NAME = "snapshots"; + /** * Generates a web request to abort a copy operation. * @@ -118,6 +121,23 @@ private static void addProperties(final HttpURLConnection request, FilePropertie BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_TYPE_HEADER, properties.getContentType()); } + /** + * Adds the share snapshot if present. + * Only for listing files and directories which requires a different query param. + * + * @param builder + * a query builder. + * @param snapshotVersion + * the share snapshot version to the query builder. + * @throws StorageException + */ + public static void addShareSnapshot(final UriQueryBuilder builder, final String snapshotVersion) + throws StorageException { + if (snapshotVersion != null) { + builder.add(Constants.QueryConstants.SHARE_SNAPSHOT, snapshotVersion); + } + } + /** * Creates a request to copy a file, Sign with 0 length. * @@ -268,14 +288,27 @@ public static HttpURLConnection deleteFile(final URI uri, final FileRequestOptio * @throws IllegalArgumentException */ public static HttpURLConnection deleteShare(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition) throws IOException, - URISyntaxException, StorageException { + final OperationContext opContext, final AccessCondition accessCondition, String snapshotVersion, DeleteShareSnapshotsOption deleteSnapshotsOption) + throws IOException, URISyntaxException, StorageException { final UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); + FileRequest.addShareSnapshot(shareBuilder, snapshotVersion); HttpURLConnection request = BaseRequest.delete(uri, fileOptions, shareBuilder, opContext); if (accessCondition != null) { accessCondition.applyConditionToRequest(request); } + switch (deleteSnapshotsOption) { + case NONE: + // nop + break; + case INCLUDE_SNAPSHOTS: + request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, + Constants.HeaderConstants.INCLUDE_SNAPSHOTS_VALUE); + break; + default: + break; + } + return request; } @@ -329,6 +362,8 @@ public static HttpURLConnection getAcl(final URI uri, final FileRequestOptions f * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. + * @param snapshotVersion + * The snapshot version, if the share is a snapshot. * @param offset * The offset at which to begin returning content. * @param count @@ -345,7 +380,7 @@ public static HttpURLConnection getAcl(final URI uri, final FileRequestOptions f * @throws IllegalArgumentException */ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition, final Long offset, + final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion, final Long offset, final Long count, boolean requestRangeContentMD5) throws IOException, URISyntaxException, StorageException { if (offset != null && requestRangeContentMD5) { @@ -354,6 +389,7 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions } final UriQueryBuilder builder = new UriQueryBuilder(); + FileRequest.addShareSnapshot(builder, snapshotVersion); final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); request.setRequestMethod(Constants.HTTP_GET); @@ -398,6 +434,8 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. * @return a HttpURLConnection to use to perform the operation. + * @param snapshotVersion + * the snapshot version to the query builder. * @throws IOException * if there is an error opening the connection * @throws URISyntaxException @@ -407,10 +445,11 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions * @throws IllegalArgumentException */ public static HttpURLConnection getFileProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition) throws StorageException, + final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException, IOException, URISyntaxException { final UriQueryBuilder builder = new UriQueryBuilder(); - return getProperties(uri, fileOptions, opContext, accessCondition, builder); + + return getProperties(uri, fileOptions, opContext, accessCondition, builder, snapshotVersion); } /** @@ -428,6 +467,8 @@ public static HttpURLConnection getFileProperties(final URI uri, final FileReque * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. + * @param snapshotVersion + * the snapshot version to the query builder. * @return a HttpURLConnection to use to perform the operation. * @throws IOException * if there is an error opening the connection @@ -438,10 +479,11 @@ public static HttpURLConnection getFileProperties(final URI uri, final FileReque * @throws IllegalArgumentException */ public static HttpURLConnection getFileRanges(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition) throws StorageException, + final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException, IOException, URISyntaxException { final UriQueryBuilder builder = new UriQueryBuilder(); + addShareSnapshot(builder, snapshotVersion); builder.add(Constants.QueryConstants.COMPONENT, RANGE_LIST_QUERY_ELEMENT_NAME); final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); @@ -469,14 +511,17 @@ public static HttpURLConnection getFileRanges(final URI uri, final FileRequestOp * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. + * @param snapshotVersion + * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ public static HttpURLConnection getShareProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition) throws IOException, URISyntaxException, + final OperationContext opContext, AccessCondition accessCondition, final String snapshotVersion) throws IOException, URISyntaxException, StorageException { final UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); - return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder); + + return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder, snapshotVersion); } /** @@ -559,12 +604,16 @@ private static UriQueryBuilder getDirectoryUriQueryBuilder() throws StorageExcep * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. + * @param snapshotVersion + * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ private static HttpURLConnection getProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition, final UriQueryBuilder builder) + final OperationContext opContext, AccessCondition accessCondition, final UriQueryBuilder builder, + String snapshotVersion) throws IOException, URISyntaxException, StorageException { + addShareSnapshot(builder, snapshotVersion); HttpURLConnection request = BaseRequest.getProperties(uri, fileOptions, builder, opContext); if (accessCondition != null) { @@ -591,7 +640,8 @@ private static HttpURLConnection getProperties(final URI uri, final FileRequestO * @param listingContext * A set of parameters for the listing operation. * @param detailsIncluded - * Additional details to return with the listing. + * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate + * whether share snapshots and/or metadata will be returned. * @return a HttpURLConnection configured for the operation. * @throws IOException * @throws URISyntaxException @@ -600,12 +650,28 @@ private static HttpURLConnection getProperties(final URI uri, final FileRequestO */ public static HttpURLConnection listShares(final URI uri, final FileRequestOptions fileOptions, final OperationContext opContext, final ListingContext listingContext, - final ShareListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException { - + final EnumSet detailsIncluded) throws URISyntaxException, IOException, StorageException { final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); - if (detailsIncluded == ShareListingDetails.ALL || detailsIncluded == ShareListingDetails.METADATA) { - builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA); + if (detailsIncluded != null && detailsIncluded.size() > 0) { + final StringBuilder sb = new StringBuilder(); + boolean started = false; + + if (detailsIncluded.contains(ShareListingDetails.SNAPSHOTS)) { + started = true; + sb.append(SNAPSHOTS_QUERY_ELEMENT_NAME); + } + + if (detailsIncluded.contains(ShareListingDetails.METADATA)) { + if (started) + { + sb.append(","); + } + + sb.append(Constants.QueryConstants.METADATA); + } + + builder.add(Constants.QueryConstants.INCLUDE, sb.toString()); } final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); @@ -741,14 +807,16 @@ public static HttpURLConnection deleteDirectory(final URI uri, final FileRequest * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the directory. + * @param snapshotVersion + * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ public static HttpURLConnection getDirectoryProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition) throws IOException, URISyntaxException, + final OperationContext opContext, AccessCondition accessCondition, String snapshotVersion) throws IOException, URISyntaxException, StorageException { final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder(); - return getProperties(uri, fileOptions, opContext, accessCondition, directoryBuilder); + return getProperties(uri, fileOptions, opContext, accessCondition, directoryBuilder, snapshotVersion); } /** @@ -767,6 +835,8 @@ public static HttpURLConnection getDirectoryProperties(final URI uri, final File * the operation. * @param listingContext * A set of parameters for the listing operation. + * @param snapshotVersion + * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws IOException * @throws URISyntaxException @@ -774,10 +844,11 @@ public static HttpURLConnection getDirectoryProperties(final URI uri, final File * @throws IllegalArgumentException */ public static HttpURLConnection listFilesAndDirectories(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final ListingContext listingContext) throws URISyntaxException, + final OperationContext opContext, final ListingContext listingContext, String snapshotVersion) throws URISyntaxException, IOException, StorageException { final UriQueryBuilder builder = getDirectoryUriQueryBuilder(); + addShareSnapshot(builder, snapshotVersion); builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); if (listingContext != null) { @@ -1099,6 +1170,49 @@ public static HttpURLConnection setFileMetadata(final URI uri, final FileRequest return setMetadata(uri, fileOptions, opContext, accessCondition, null); } + /** + * Constructs a HttpURLConnection to create a snapshot of the share. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param fileOptions + * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudFileClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the share. + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + */ + public static HttpURLConnection snapshotShare(final URI uri, final FileRequestOptions fileOptions, + final OperationContext opContext, final AccessCondition accessCondition) throws IOException, + URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.RESOURCETYPE, "share"); + builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.SNAPSHOT); + final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + } + + return request; + } + /** * Constructs a HttpURLConnection to set the file's properties, Sign with zero length specified. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java index 40a1c51..14d7bcf 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java @@ -205,6 +205,17 @@ static Integer parseShareQuota(final HttpURLConnection request) { return (shareQuota == -1) ? null : shareQuota; } + /** + * Gets the snapshot ID from the request header. + * + * @param request + * The response from server. + * @return the snapshot ID from the request header. + */ + public static String getSnapshotTime(final HttpURLConnection request) { + return request.getHeaderField(Constants.HeaderConstants.SNAPSHOT_ID_HEADER); + } + /** * Private Default Ctor */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java index c5e29c6..e834085 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java @@ -17,7 +17,6 @@ import java.util.Date; import com.microsoft.azure.storage.AccessCondition; -import com.microsoft.azure.storage.core.Utility; /** * Represents the system properties for a share. @@ -39,6 +38,27 @@ public final class FileShareProperties { */ private Integer shareQuota; + /** + * Creates an instance of the FileShareProperties class. + */ + public FileShareProperties() { + } + + /** + * Creates an instance of the FileShareProperties class by copying values from another + * FileShareProperties instance. + * + * @param other + * A {@link FileShareProperties} object which represents the file share properties to copy. + */ + public FileShareProperties(final FileShareProperties other) { + if (other != null) { + this.setEtag(other.getEtag()); + this.setLastModified(other.getLastModified()); + this.setShareQuota(other.getShareQuota()); + } + } + /** * Gets the ETag value of the share. *

@@ -102,9 +122,6 @@ protected void setLastModified(final Date lastModified) { * the size of files stored on the share. */ public void setShareQuota(Integer shareQuota) { - if (shareQuota != null) { - Utility.assertInBounds("Share Quota", shareQuota, 1, FileConstants.MAX_SHARE_QUOTA); - } this.shareQuota = shareQuota; } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java index c73693d..3d3b9b9 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java @@ -46,6 +46,7 @@ final class ShareListHandler extends DefaultHandler { private final ListResponse response = new ListResponse(); private FileShareAttributes attributes; private String shareName; + private String snapshotID; private ShareListHandler(CloudFileClient serviceClient) { this.serviceClient = serviceClient; @@ -77,6 +78,7 @@ public void startElement(String uri, String localName, String qName, Attributes if (FileConstants.SHARE_ELEMENT.equals(localName)) { this.shareName = Constants.EMPTY_STRING; + this.snapshotID = null; this.attributes = new FileShareAttributes(); } } @@ -105,6 +107,7 @@ public void endElement(String uri, String localName, String qName) throws SAXExc CloudFileShare retShare = this.serviceClient.getShareReference(this.shareName); retShare.setMetadata(this.attributes.getMetadata()); retShare.setProperties(this.attributes.getProperties()); + retShare.snapshotID = this.snapshotID; this.response.getResults().add(retShare); } @@ -134,6 +137,9 @@ else if (FileConstants.SHARE_ELEMENT.equals(parentNode)) { if (Constants.NAME_ELEMENT.equals(currentNode)) { this.shareName = value; } + else if (Constants.QueryConstants.SNAPSHOT.equals(currentNode.toLowerCase())) { + this.snapshotID = value; + } } else if (Constants.PROPERTIES.equals(parentNode)) { try { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java index b9b54dd..244b11f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java @@ -18,10 +18,6 @@ * Specifies which details to include when listing the shares in this storage account. */ public enum ShareListingDetails { - /** - * Specifies including all available details. - */ - ALL(1), /** * Specifies including share metadata. @@ -29,9 +25,9 @@ public enum ShareListingDetails { METADATA(1), /** - * Specifies including no additional details. + * Specifies listing share snapshots. */ - NONE(0); + SNAPSHOTS(2); /** * Returns the value of this enum. From 164d4213ee49a78e99d89e3f5f690132524e5f5b Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 12 Apr 2017 11:08:11 -0700 Subject: [PATCH 02/30] Fixed MD5 and OpenWriteExisting bugs. --- ChangeLog.txt | 2 + .../storage/blob/CloudPageBlobTests.java | 26 +++++++++++++ .../azure/storage/file/CloudFileTests.java | 39 +++++++++++++++++++ .../azure/storage/blob/CloudPageBlob.java | 3 +- .../com/microsoft/azure/storage/core/SR.java | 2 +- .../azure/storage/file/CloudFile.java | 17 ++++---- 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index c724746..9302c17 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,8 @@ 2017.XX.XX Version 2.0.0 * Added support for taking a snapshot of a share. * Fixed Exists() calls on Shares and Directories to now populate metadata. This was already being done for Files. + * Fixed a bug that prevented setting content MD5 to true when creating a new file. + * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. 2017.01.30 Version 1.0.0 * Android Studio support via Gradle build system. 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 f8ace48..4d1bd7c 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 @@ -211,6 +211,32 @@ public void testPageBlobDownloadRangeValidationTest() throws StorageException, U assertEquals(100, downloadLength); } + /** + * Test requesting stored content MD5 with OpenWriteExisting(). + * + * @throws URISyntaxException + * @throws StorageException + */ + @Test + public void testPageOpenWriteExistingWithMD5() throws URISyntaxException, StorageException, IOException { + final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob"); + final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(pageBlobName); + pageBlobRef.create(512); + + BlobRequestOptions options = new BlobRequestOptions(); + options.setStoreBlobContentMD5(true); + options.setDisableContentMD5Validation(false); + + try + { + pageBlobRef.openWriteExisting(null, options, null); + fail("Expect failure due to requesting MD5 calculation"); + } + catch (IllegalArgumentException e) + { + } + } + @Test public void testPageBlobUploadFromStreamTest() throws URISyntaxException, StorageException, IOException { final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob"); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java index e85239d..4b4dcc3 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java @@ -729,6 +729,45 @@ public void testFileUploadMD5Validation() throws URISyntaxException, StorageExce assertEquals(calculatedMD5, fileRef2.getProperties().getContentMD5()); } + @Test + public void testFileContentMD5NewFileTest() throws URISyntaxException, StorageException, IOException { + final String fileName = FileTestHelper.generateRandomFileName(); + final CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName); + + FileRequestOptions options = new FileRequestOptions(); + options.setStoreFileContentMD5(true); + options.setDisableContentMD5Validation(false); + + File tempFile = File.createTempFile("sourceFile", ".tmp"); + file.uploadFromFile(tempFile.getAbsolutePath(), null, options, null); + } + + /** + * Test requesting stored content MD5 with OpenWriteExisting(). + * + * @throws URISyntaxException + * @throws StorageException + */ + @Test + public void testCloudFileOpenWriteExistingWithMD5() throws URISyntaxException, StorageException, IOException { + String fileName = FileTestHelper.generateRandomFileName(); + final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName); + fileRef.create(512); + + FileRequestOptions options = new FileRequestOptions(); + options.setStoreFileContentMD5(true); + options.setDisableContentMD5Validation(false); + + try + { + fileRef.openWriteExisting(null, options, null); + fail("Expect failure due to requesting MD5 calculation"); + } + catch (IllegalArgumentException e) + { + } + } + @Test public void testFileEmptyHeaderSigningTest() throws URISyntaxException, StorageException, IOException { final String fileName = FileTestHelper.generateRandomFileName(); 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 f5a2d83..4243a64 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 @@ -826,8 +826,7 @@ public BlobOutputStream openWriteExisting() throws StorageException { @DoesServiceRequest public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { - return this - .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); + return this.openOutputStreamInternal(null /* length */, accessCondition, options, opContext); } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java index 395d8c9..24fd758 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java @@ -80,7 +80,7 @@ public class SR { public static final String INVALID_CORS_RULE = "A CORS rule must contain at least one allowed origin and allowed method, and MaxAgeInSeconds cannot have a value less than zero."; public static final String INVALID_DATE_STRING = "Invalid Date String: %s."; public static final String INVALID_EDMTYPE_VALUE = "Invalid value '%s' for EdmType."; - public static final String INVALID_FILE_LENGTH = "File length must be greater than 0 bytes."; + public static final String INVALID_FILE_LENGTH = "File length must be greater than or equal to 0 bytes."; public static final String INVALID_GEO_REPLICATION_STATUS = "Null or Invalid geo-replication status in response: %s."; public static final String INVALID_IP_ADDRESS = "Error when parsing IPv4 address: IP address '%s' is invalid."; public static final String INVALID_KEY = "Storage Key is not a valid base64 encoded string."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index ade5d09..fccfc3a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1881,9 +1881,8 @@ public FileOutputStream openWriteExisting() throws StorageException, URISyntaxEx */ @DoesServiceRequest public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { - return this - .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); + OperationContext opContext) throws StorageException { + return this.openOutputStreamInternal(null /* length */, accessCondition, options, opContext); } /** @@ -1978,13 +1977,13 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient, false /* setStartTime */); if (length != null) { + this.create(length, accessCondition, options, opContext); + } + else { if (options.getStoreFileContentMD5()) { throw new IllegalArgumentException(SR.FILE_MD5_NOT_POSSIBLE); } - this.create(length, accessCondition, options, opContext); - } - else { this.downloadAttributes(accessCondition, options, opContext); length = this.getProperties().getLength(); } @@ -2612,8 +2611,8 @@ public void upload(final InputStream sourceStream, final long length) throws Sto * @param sourceStream * An {@link InputStream} object to read from. * @param length - * A long which represents the length, in bytes, of the stream data. This must be great than - * zero. + * A long which represents the length, in bytes, of the stream data. This must be greater than + * or equal to zero. * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the file. * @param options @@ -2642,7 +2641,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - if (length <= 0) { + if (length < 0) { throw new IllegalArgumentException(SR.INVALID_FILE_LENGTH); } From a1ffb485a42a659ed3997eb391b67216828feaae Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 11 Apr 2017 15:11:32 -0700 Subject: [PATCH 03/30] exposing get snapshot to CloudFileShare --- .../com/microsoft/azure/storage/file/CloudFileShare.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java index 3f1267b..df87997 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java @@ -1495,6 +1495,15 @@ public URI getUri() { return this.storageUri.getPrimaryUri(); } + /** + * Returns the snapshotID for this share. + * + * @return The snapshotID as a string for this share. + */ + public final String getSnapshot() { + return this.snapshotID; + } + /** * Indicates whether this share is a snapshot. * From c855da62d53ef7245653268275a1c196acb9bd66 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 12 Apr 2017 13:12:00 -0700 Subject: [PATCH 04/30] Share snapshot fix when getting server response --- .../src/com/microsoft/azure/storage/file/CloudFileShare.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java index df87997..399b787 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java @@ -838,7 +838,7 @@ public CloudFileShare preProcessResponse(CloudFileShare share, CloudFileClient c // use the specified metadata if not null : otherwise share's metadata snapshot.setMetadata(metadata != null ? metadata : share.metadata); - share.updatePropertiesFromResponse(this.getConnection()); + snapshot.updatePropertiesFromResponse(this.getConnection()); return snapshot; } From 51eea2dcf25f4feb6a607d4d21d8b07eefc86ad0 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Sun, 28 May 2017 09:00:15 -0700 Subject: [PATCH 05/30] Revert "Share Snapshot Support" This reverts commit 0c5df282ff7093583146da4a03571a2ecbcb1334. --- ChangeLog.txt | 4 +- .../storage/file/CloudFileClientTests.java | 51 +-- .../storage/file/CloudFileDirectoryTests.java | 81 +---- .../storage/file/CloudFileShareTests.java | 198 +---------- .../azure/storage/file/CloudFileTests.java | 203 +++-------- .../azure/storage/file/FileTestHelper.java | 6 +- .../microsoft/azure/storage/Constants.java | 14 +- .../storage/StorageErrorCodeStrings.java | 5 - .../azure/storage/blob/BlobConstants.java | 5 + .../azure/storage/blob/BlobRequest.java | 2 +- .../com/microsoft/azure/storage/core/SR.java | 1 - .../core/SharedAccessSignatureHelper.java | 8 +- .../azure/storage/file/CloudFile.java | 151 +++----- .../azure/storage/file/CloudFileClient.java | 55 +-- .../storage/file/CloudFileDirectory.java | 77 ++-- .../azure/storage/file/CloudFileShare.java | 332 +----------------- .../file/DeleteShareSnapshotsOption.java | 35 -- .../azure/storage/file/FileOutputStream.java | 8 +- .../azure/storage/file/FileRequest.java | 148 +------- .../azure/storage/file/FileResponse.java | 11 - .../storage/file/FileShareProperties.java | 25 +- .../azure/storage/file/ShareListHandler.java | 6 - .../storage/file/ShareListingDetails.java | 8 +- 23 files changed, 197 insertions(+), 1237 deletions(-) delete mode 100644 microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 9302c17..f1488a3 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,4 @@ -2017.XX.XX Version 2.0.0 - * Added support for taking a snapshot of a share. - * Fixed Exists() calls on Shares and Directories to now populate metadata. This was already being done for Files. +2017.XX.XX Version 1.1.0 * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java index d17a3c0..77db677 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileClientTests.java @@ -27,8 +27,6 @@ import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; import java.util.UUID; import static org.junit.Assert.*; @@ -65,8 +63,8 @@ public void testListSharesTest() throws StorageException, URISyntaxException { ResultContinuation token = null; do { - ResultSegment segment = fileClient.listSharesSegmented(prefix, - EnumSet.allOf(ShareListingDetails.class), 15, token, null, null); + ResultSegment segment = fileClient.listSharesSegmented(prefix, ShareListingDetails.ALL, + 15, token, null, null); for (final CloudFileShare share : segment.getResults()) { share.downloadAttributes(); @@ -103,7 +101,7 @@ public void testListSharesMaxResultsValidationTest() throws StorageException, UR for (int i = 0; i >= -2; i--) { try{ fileClient.listSharesSegmented( - prefix, EnumSet.allOf(ShareListingDetails.class), i, null, null, null); + prefix, ShareListingDetails.ALL, i, null, null, null); fail(); } catch (IllegalArgumentException e) { @@ -113,47 +111,4 @@ public void testListSharesMaxResultsValidationTest() throws StorageException, UR } assertNotNull(fileClient.listSharesSegmented("thereshouldntbeanyshareswiththisprefix")); } - - @Test - public void testListSharesWithSnapshot() throws StorageException, URISyntaxException { - CloudFileClient fileClient = FileTestHelper.createCloudFileClient(); - CloudFileShare share = fileClient.getShareReference(UUID.randomUUID().toString()); - share.create(); - - HashMap shareMeta = new HashMap(); - shareMeta.put("key1", "value1"); - share.setMetadata(shareMeta); - share.uploadMetadata(); - - CloudFileShare snapshot = share.createSnapshot(); - HashMap meta2 = new HashMap(); - meta2.put("key2", "value2"); - share.setMetadata(meta2); - share.uploadMetadata(); - - CloudFileClient client = FileTestHelper.createCloudFileClient(); - Iterable listResult = client.listShares(share.name, EnumSet.allOf(ShareListingDetails.class), null, null); - - int count = 0; - boolean originalFound = false; - boolean snapshotFound = false; - for (CloudFileShare listShareItem : listResult) { - if (listShareItem.getName().equals(share.getName()) && !listShareItem.isSnapshot() && !originalFound) - { - count++; - originalFound = true; - assertEquals(share.getMetadata(), listShareItem.getMetadata()); - assertEquals(share.getStorageUri(), listShareItem.getStorageUri()); - } - else if (listShareItem.getName().equals(share.getName()) && - listShareItem.isSnapshot() && !snapshotFound) { - count++; - snapshotFound = true; - assertEquals(snapshot.getMetadata(), listShareItem.getMetadata()); - assertEquals(snapshot.getStorageUri(), listShareItem.getStorageUri()); - } - } - - assertEquals(2, count); - } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java index 7d6df9a..da5373b 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileDirectoryTests.java @@ -26,21 +26,19 @@ import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.core.PathUtility; import com.microsoft.azure.storage.core.SR; -import com.microsoft.azure.storage.core.UriQueryBuilder; import junit.framework.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashMap; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; import static org.junit.Assert.*; @@ -462,7 +460,7 @@ public void testCloudFileDirectoryInvalidMetadata() throws StorageException, URI testMetadataFailures(directory, "key1", "\n \t", false); } - private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) throws URISyntaxException { + private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) { directory.getMetadata().put(key, value); try { directory.uploadMetadata(); @@ -480,71 +478,6 @@ private static void testMetadataFailures(CloudFileDirectory directory, String ke directory.getMetadata().remove(key); } - @Test - public void testUnsupportedDirectoryApisWithinShareSnapshot() throws StorageException, URISyntaxException { - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFileDirectory rootDir = snapshot.getRootDirectoryReference(); - try { - rootDir.create(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - rootDir.delete(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - rootDir.uploadMetadata(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - - snapshot.delete(); - } - - @Test - public void testSupportedDirectoryApisInShareSnapshot() throws StorageException, URISyntaxException { - CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); - dir.deleteIfExists(); - dir.create(); - HashMap meta = new HashMap(); - meta.put("key1", "value1"); - dir.setMetadata(meta); - dir.uploadMetadata(); - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFileDirectory snapshotDir = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); - - HashMap meta2 = new HashMap(); - meta2.put("key2", "value2"); - dir.setMetadata(meta2); - dir.uploadMetadata(); - snapshotDir.downloadAttributes(); - - assertTrue(snapshotDir.getMetadata().size() == 1 && snapshotDir.getMetadata().get("key1").equals("value1")); - assertNotNull(snapshotDir.getProperties().getEtag()); - - dir.downloadAttributes(); - assertTrue(dir.getMetadata().size() == 1 && dir.getMetadata().get("key2").equals("value2")); - assertNotNull(dir.getProperties().getEtag()); - assertNotEquals(dir.getProperties().getEtag(), snapshotDir.getProperties().getEtag()); - - final UriQueryBuilder uriBuilder = new UriQueryBuilder(); - uriBuilder.add("sharesnapshot", snapshot.snapshotID); - uriBuilder.add("restype", "directory"); - CloudFileDirectory snapshotDir2 = new CloudFileDirectory(uriBuilder.addToURI(dir.getUri()), this.share.getServiceClient().getCredentials()); - assertEquals(snapshot.snapshotID, snapshotDir2.getShare().snapshotID); - assertTrue(snapshotDir2.exists()); - - snapshot.delete(); - } - /* [TestMethod] [Description("CloudFileDirectory deleting a directory using conditional access")] @@ -867,8 +800,6 @@ public void eventOccurred(SendingRequestEvent eventArg) { } catch (StorageException e) { fail("Delete should succeed."); - } catch (URISyntaxException e) { - fail("Delete should succeed."); } } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java index 6147210..49df9d4 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileShareTests.java @@ -25,14 +25,12 @@ import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.TestRunners.SlowTests; import com.microsoft.azure.storage.core.SR; -import com.microsoft.azure.storage.core.UriQueryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URISyntaxException; @@ -51,6 +49,7 @@ @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public class CloudFileShareTests { + protected static CloudFileClient client; protected CloudFileShare share; @Before @@ -60,7 +59,7 @@ public void fileShareTestMethodSetUp() throws StorageException, URISyntaxExcepti @After public void fileShareTestMethodTearDown() throws StorageException { - //this.share.deleteIfExists(); + this.share.deleteIfExists(); } /** @@ -315,7 +314,7 @@ public void testCloudFileShareUploadMetadata() throws StorageException, URISynta assertEquals("value2", this.share.getMetadata().get("key2")); Iterable shares = this.share.getServiceClient().listShares(this.share.getName(), - EnumSet.of(ShareListingDetails.METADATA), null, null); + ShareListingDetails.METADATA, null, null); for (CloudFileShare share3 : shares) { assertEquals(2, share3.getMetadata().size()); @@ -426,7 +425,6 @@ public void testCloudFileShareQuota() throws StorageException, URISyntaxExceptio try { shareQuota = FileConstants.MAX_SHARE_QUOTA + 1; this.share.getProperties().setShareQuota(shareQuota); - this.share.uploadProperties(); fail(); } catch (IllegalArgumentException e) { assertEquals(String.format(SR.PARAMETER_NOT_IN_RANGE, "Share Quota", 1, FileConstants.MAX_SHARE_QUOTA), @@ -491,196 +489,6 @@ public void eventOccurred(SendingRequestEvent eventArg) { assertTrue(this.share.deleteIfExists(null, null, ctx)); } - @Test - public void testCreateShareSnapshot() throws StorageException, URISyntaxException, IOException { - // create share with metadata - this.share.create(); - assertTrue(this.share.exists()); - HashMap shareMeta = new HashMap(); - shareMeta.put("key1", "value1"); - this.share.setMetadata(shareMeta); - this.share.uploadMetadata(); - - CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); - dir1.create(); - CloudFile file1 = dir1.getFileReference("file1"); - file1.create(1024); - ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(1024); - file1.upload(srcStream, 1024); - - // create directory with metadata - HashMap dirMeta = new HashMap(); - dirMeta.put("key2", "value2"); - dir1.setMetadata(dirMeta); - dir1.uploadMetadata(); - - // verify that exists() call on snapshot populates metadata - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFileClient client = FileTestHelper.createCloudFileClient(); - CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); - assertTrue(snapshotRef.exists()); - assertTrue(snapshotRef.getMetadata().size() == 1 && snapshotRef.getMetadata().get("key1").equals("value1")); - - // verify that downloadAttributes() populates metadata - CloudFileShare snapshotRef2 = client.getShareReference(snapshot.name, snapshot.snapshotID); - snapshotRef2.downloadAttributes(); - snapshot.downloadAttributes(); - assertTrue(snapshotRef2.getMetadata().size() == 1 && snapshotRef2.getMetadata().get("key1").equals("value1")); - assertTrue(snapshot.getMetadata().size() == 1 && snapshot.getMetadata().get("key1").equals("value1")); - - // verify that exists() populates the metadata - CloudFileDirectory snapshotDir1 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); - snapshotDir1.exists(); - assertTrue(snapshotDir1.getMetadata().size() == 1 && snapshotDir1.getMetadata().get("key2").equals("value2")); - - // verify that downloadAttributes() populates the metadata - CloudFileDirectory snapshotDir2 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1"); - snapshotDir2.downloadAttributes(); - assertTrue(snapshotDir2.getMetadata().size() == 1 && snapshotDir2.getMetadata().get("key2").equals("value2")); - - // create snapshot with metadata - HashMap shareMeta2 = new HashMap(); - shareMeta2.put("abc", "def"); - CloudFileShare snapshotRef3 = this.share.createSnapshot(shareMeta2, null, null, null); - CloudFileShare snapshotRef4 = client.getShareReference(snapshotRef3.name, snapshotRef3.snapshotID); - assertTrue(snapshotRef4.exists()); - assertTrue(snapshotRef4.getMetadata().size() == 1 && snapshotRef4.getMetadata().get("abc").equals("def")); - - final UriQueryBuilder uriBuilder = new UriQueryBuilder(); - uriBuilder.add("sharesnapshot", snapshot.snapshotID); - CloudFileShare snapshotRef5 = new CloudFileShare(uriBuilder.addToURI(this.share.getUri()), - this.share.getServiceClient().getCredentials(), null); - assertEquals(snapshot.snapshotID, snapshotRef5.snapshotID); - assertTrue(snapshotRef5.exists()); - - snapshot.delete(); - } - - @Test - public void testDeleteShareSnapshotOptions() throws StorageException, URISyntaxException, IOException { - // create share with metadata - this.share.create(); - assertTrue(this.share.exists()); - - // verify that exists() call on snapshot populates metadata - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFileClient client = FileTestHelper.createCloudFileClient(); - CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); - assertTrue(snapshotRef.exists()); - - try { - share.delete(); - } - catch (final StorageException e) { - assertEquals(StorageErrorCodeStrings.SHARE_HAS_SNAPSHOTS, e.getErrorCode()); - } - - share.delete(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null); - assertFalse(share.exists()); - assertFalse(snapshot.exists()); - } - - @Test - public void testListFilesAndDirectoriesWithinShareSnapshot() throws StorageException, URISyntaxException { - this.share.create(); - - CloudFileDirectory myDir = this.share.getRootDirectoryReference().getDirectoryReference("mydir"); - myDir.create(); - myDir.getFileReference("myfile").create(1024); - myDir.getDirectoryReference("yourDir").create(); - assertTrue(this.share.exists()); - - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFileClient client = FileTestHelper.createCloudFileClient(); - CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID); - - Iterable listResult = snapshotRef.getRootDirectoryReference().listFilesAndDirectories(); - int count = 0; - for (ListFileItem listFileItem : listResult) { - count++; - assertEquals("mydir", ((CloudFileDirectory) listFileItem).getName()); - } - - assertEquals(1, count); - - count = 0; - listResult = snapshotRef.getRootDirectoryReference().getDirectoryReference("mydir").listFilesAndDirectories(); - for (ListFileItem listFileItem : listResult) { - if (listFileItem instanceof CloudFileDirectory) { - count++; - assertEquals("yourDir", ((CloudFileDirectory) listFileItem).getName()); - } - else { - count++; - assertEquals("myfile", ((CloudFile) listFileItem).getName()); - } - } - - assertEquals(2, count); - - snapshot.delete(); - } - - @Test - public void testUnsupportedApisShareSnapshot() throws StorageException, URISyntaxException { - CloudFileClient client = FileTestHelper.createCloudFileClient(); - this.share.create(); - this.share.downloadPermissions(); - CloudFileShare snapshot = this.share.createSnapshot(); - try { - snapshot.createSnapshot(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - new CloudFileShare(snapshot.getQualifiedUri(), client.getCredentials(), "2016-10-24T16:37:17.0000000Z"); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED, e.getMessage()); - } - try { - snapshot.downloadPermissions(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - snapshot.getStats(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - snapshot.uploadMetadata(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - FileSharePermissions permissions = new FileSharePermissions(); - snapshot.uploadPermissions(permissions); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - snapshot.uploadProperties(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - - snapshot.delete(); - } - private static void assertPermissionsEqual(FileSharePermissions expected, FileSharePermissions actual) { HashMap expectedPolicies = expected.getSharedAccessPolicies(); HashMap actualPolicies = actual.getSharedAccessPolicies(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java index 4b4dcc3..1556854 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java @@ -1,11 +1,11 @@ /** * 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. @@ -15,10 +15,6 @@ package com.microsoft.azure.storage.file; import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.core.Base64; -import com.microsoft.azure.storage.core.SR; -import com.microsoft.azure.storage.core.Utility; -import com.microsoft.azure.storage.core.UriQueryBuilder; import com.microsoft.azure.storage.NameValidator; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RetryNoRetry; @@ -39,10 +35,8 @@ import com.microsoft.azure.storage.blob.CloudPageBlob; import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions; import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; -import com.microsoft.azure.storage.TestRunners.CloudTests; -import com.microsoft.azure.storage.TestRunners.DevFabricTests; -import com.microsoft.azure.storage.TestRunners.DevStoreTests; -import com.microsoft.azure.storage.TestRunners.SlowTests; +import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.Utility; import org.junit.After; import org.junit.Before; @@ -53,7 +47,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.HttpURLConnection; import java.net.URI; @@ -113,7 +106,7 @@ public void CloudFileNameValidation() invalidFileTestHelper("", "Between 1 and 255 characters.", "Invalid file name. The name may not be null, empty, or whitespace only."); invalidFileTestHelper(new String(new char[256]).replace("\0", "n"), "Between 1 and 255 characters.", "Invalid file name length. The name must be between 1 and 255 characters long."); } - + private void invalidFileTestHelper(String fileName, String failMessage, String exceptionMessage) { try @@ -126,10 +119,10 @@ private void invalidFileTestHelper(String fileName, String failMessage, String e assertEquals(exceptionMessage, e.getMessage()); } } - + /** * Test file creation and deletion. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -158,7 +151,7 @@ public void testCloudFileCreate() throws StorageException, URISyntaxException { /** * Test file constructor. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -182,7 +175,7 @@ public void testCloudFileConstructor() throws URISyntaxException, StorageExcepti /** * Test file resizing. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -215,7 +208,7 @@ public void testCloudFileResize() throws URISyntaxException, StorageException { /** * Test file creation with invalid sizes. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -243,7 +236,7 @@ public void testCloudFileCreateInvalidSize() throws StorageException, URISyntaxE /** * Test file deleteIfExists. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -259,7 +252,7 @@ public void testCloudFileDeleteIfExists() throws URISyntaxException, StorageExce /** * Test file exits method. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -282,7 +275,7 @@ public void testCloudFileExists() throws URISyntaxException, StorageException { /** * Test file getProperties. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -317,7 +310,7 @@ public void testCloudFileDownloadAttributes() throws URISyntaxException, Storage /** * Test file setProperties. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -371,7 +364,7 @@ public void testCloudFileSetProperties() throws StorageException, URISyntaxExcep /** * Test file creation with metadata. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -490,23 +483,23 @@ public void testCopyWithChineseChars() throws StorageException, IOException, URI @Override public void eventOccurred(SendingRequestEvent eventArg) { HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); - + try { CloudFileDirectory rootDirectory = CloudFileTests.this.share.getRootDirectoryReference(); - + // Test the copy destination request url assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt", con.getURL().toString()); - + // Test the copy source request property assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", con.getRequestProperty("x-ms-copy-source")); } catch (Exception e) { fail("This code should not generate any exceptions."); - } + } } }); - + copyDestination.startCopy(copySource.getUri(), null, null, null, ctx); copyDestination.startCopy(copySource, null, null, null, ctx); } @@ -519,7 +512,7 @@ public void testCopyFileWithMetadataOverride() throws URISyntaxException, Storag String data = "String data"; CloudFile source = this.share.getRootDirectoryReference().getFileReference("source"); FileTestHelper.setFileProperties(source); - + // do this to make sure the set MD5 can be compared, otherwise when the dummy value // doesn't match the actual MD5 an exception would be thrown FileRequestOptions options = new FileRequestOptions(); @@ -565,7 +558,7 @@ public void testCopyFileWithMetadataOverride() throws URISyntaxException, Storag /** * Start copying a file and then abort - * + * * @throws StorageException * @throws URISyntaxException * @throws IOException @@ -592,7 +585,7 @@ public void testCopyFileAbort() throws StorageException, URISyntaxException, IOE /** * Test file stream uploading. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -618,7 +611,7 @@ public void testFileUploadFromStreamTest1() throws URISyntaxException, StorageEx /** * Create a file and try to download a range of its contents - * + * * @throws StorageException * @throws URISyntaxException * @throws IOException @@ -792,7 +785,7 @@ public void eventOccurred(SendingRequestEvent eventArg) { /** * Test downloading a file range. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -923,7 +916,7 @@ public void testCloudFileUploadFromStreamWithAccessCondition() throws URISyntaxE /** * Test file input stream. - * + * * @throws URISyntaxException * @throws StorageException * @throws IOException @@ -962,7 +955,7 @@ public void testCloudFileInputStream() throws URISyntaxException, StorageExcepti /** * Test file uploading from byte arrays. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -979,7 +972,7 @@ public void testCloudFileUploadFromByteArray() throws Exception { /** * Test file upload and download using text. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -995,7 +988,7 @@ public void testCloudFileUploadDownloadFromText() throws IOException, StorageExc /** * Test file upload and download using io files. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1028,7 +1021,7 @@ private void doUploadFromByteArrayTest(CloudFile file, int bufferSize, int buffe } } - private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOException, StorageException, URISyntaxException { + private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOException, StorageException { File sourceFile = File.createTempFile("sourceFile", ".tmp"); File destinationFile = new File(sourceFile.getParentFile(), "destinationFile.tmp"); @@ -1065,7 +1058,7 @@ private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOExc /** * Test file range uploads. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1118,7 +1111,7 @@ public void testCloudFileUploadRange() throws URISyntaxException, StorageExcepti /** * Test clearing file ranges. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1157,7 +1150,7 @@ public void testCloudFileClearRange() throws URISyntaxException, StorageExceptio /** * Test file resizing. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1187,7 +1180,7 @@ public void testCloudFileResize2() throws StorageException, URISyntaxException { /** * Test file range downloading. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1227,7 +1220,7 @@ public void testCloudFileDownloadRange() throws StorageException, URISyntaxExcep /** * Test downloadAttributes. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1239,7 +1232,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio FileRequestOptions options = new FileRequestOptions(); options.setDisableContentMD5Validation(true); - // with explicit upload/download of properties + // with explicit upload/download of properties String fileName1 = FileTestHelper.generateRandomFileName(); CloudFile fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1); @@ -1254,7 +1247,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio FileTestHelper.assertAreEqual(props1, props2); - // by uploading/downloading the file + // by uploading/downloading the file fileName1 = FileTestHelper.generateRandomFileName(); fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1); @@ -1271,7 +1264,7 @@ public void testCloudFileUploadDownloadFileProperties() throws URISyntaxExceptio /** * Test FileOutputStream. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1311,7 +1304,7 @@ public void testCloudFileOpenOutputStream() throws URISyntaxException, StorageEx /** * Test FileOutputStream. - * + * * @throws URISyntaxException * @throws StorageException */ @@ -1338,7 +1331,7 @@ public void testCloudFileOpenOutputStreamNoArgs() throws URISyntaxException, Sto /** * Test specific deleteIfExists case. - * + * * @throws URISyntaxException * @throws StorageException * @throws IOException @@ -1383,8 +1376,6 @@ public void eventOccurred(SendingRequestEvent eventArg) { } catch (StorageException e) { fail("Delete should succeed."); - } catch (URISyntaxException e) { - fail("Delete should succeed."); } } } @@ -1394,7 +1385,7 @@ public void eventOccurred(SendingRequestEvent eventArg) { file.create(2); assertFalse(file.deleteIfExists(null, null, ctx)); } - + /** * @throws StorageException * @throws URISyntaxException @@ -1410,7 +1401,7 @@ public void testFileNamePlusEncoding() throws StorageException, URISyntaxExcepti copyFile.startCopy(originalFile); FileTestHelper.waitForCopy(copyFile); - + copyFile.downloadAttributes(); originalFile.downloadAttributes(); FileProperties prop1 = copyFile.getProperties(); @@ -1423,12 +1414,12 @@ public void testFileNamePlusEncoding() throws StorageException, URISyntaxExcepti assertEquals(prop1.getContentMD5(), prop2.getContentMD5()); assertEquals(prop1.getContentType(), prop2.getContentType()); } - + private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception { Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); cal.setTime(new Date()); cal.add(Calendar.MINUTE, 5); - + // Source SAS must have read permissions SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ)); @@ -1438,14 +1429,14 @@ private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception // Get destination reference final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination"); - + // Start copy and wait for completion StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken); Constructor blobType = source.getClass().getConstructor(URI.class); String copyId = destination.startCopy(blobType.newInstance(credentials.transformUri(source.getUri()))); FileTestHelper.waitForCopy(destination); destination.downloadAttributes(); - + // Check original file references for equality assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus()); assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(), @@ -1563,108 +1554,8 @@ private void doCloudFileCopy(boolean sourceIsSas, boolean destinationIsSas) assertEquals(prop1.getContentType(), prop2.getContentType()); assertEquals("value", destination.getMetadata().get("Test")); - + destination.delete(); source.delete(); } - - @Test - public void testUnsupportedFileApisWithinShareSnapshot() throws StorageException, URISyntaxException { - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFile file = snapshot.getRootDirectoryReference().getFileReference("file"); - - try { - file.create(1024); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.delete(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.uploadMetadata(); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.abortCopy(null); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.clearRange(0, 512); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.startCopy(file); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } - try { - file.upload(null, 512); - fail("Shouldn't get here"); - } - catch (IllegalArgumentException e) { - assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage()); - } catch (IOException e) { - fail("Shouldn't get here"); - } - - snapshot.delete(); - } - - @Test - public void testSupportedFileApisInShareSnapshot() throws StorageException, URISyntaxException, UnsupportedEncodingException { - CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1"); - dir.deleteIfExists(); - dir.create(); - CloudFile file = dir.getFileReference("file"); - file.create(1024); - - HashMap meta = new HashMap(); - meta.put("key1", "value1"); - file.setMetadata(meta); - file.uploadMetadata(); - - CloudFileShare snapshot = this.share.createSnapshot(); - CloudFile snapshotFile = snapshot.getRootDirectoryReference() - .getDirectoryReference("dir1").getFileReference("file"); - - HashMap meta2 = new HashMap(); - meta2.put("key2", "value2"); - file.setMetadata(meta2); - file.uploadMetadata(); - snapshotFile.downloadAttributes(); - - assertTrue(snapshotFile.getMetadata().size() == 1 && snapshotFile.getMetadata().get("key1").equals("value1")); - assertNotNull(snapshotFile.getProperties().getEtag()); - - file.downloadAttributes(); - assertTrue(file.getMetadata().size() == 1 && file.getMetadata().get("key2").equals("value2")); - assertNotNull(file.getProperties().getEtag()); - assertNotEquals(file.getProperties().getEtag(), snapshotFile.getProperties().getEtag()); - - final UriQueryBuilder uriBuilder = new UriQueryBuilder(); - uriBuilder.add("sharesnapshot", snapshot.snapshotID); - CloudFile snapshotFile2 = new CloudFile(uriBuilder.addToURI(file.getUri()), this.share.getServiceClient().getCredentials()); - assertEquals(snapshot.snapshotID, snapshotFile2.getShare().snapshotID); - assertTrue(snapshotFile2.exists()); - - snapshot.delete(); - } } \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java index 44feb8f..df469d7 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/FileTestHelper.java @@ -96,7 +96,7 @@ static StorageUri ensureTrailingSlash(StorageUri uri) throws URISyntaxException } protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSize, int bufferOffset) - throws StorageException, IOException, URISyntaxException { + throws StorageException, IOException { final Random randGenerator = new Random(); final byte[] buffer = new byte[fileSize]; randGenerator.nextBytes(buffer); @@ -118,7 +118,7 @@ protected static void doDownloadTest(CloudFile file, int fileSize, int bufferSiz } protected static void doDownloadRangeToByteArrayTest(CloudFile file, int fileSize, int bufferSize, - int bufferOffset, Long fileOffset, Long length) throws IOException, StorageException, URISyntaxException { + int bufferOffset, Long fileOffset, Long length) throws IOException, StorageException { final Random randGenerator = new Random(); final byte[] buffer = new byte[fileSize]; randGenerator.nextBytes(buffer); @@ -152,7 +152,7 @@ protected static void doDownloadRangeToByteArrayTest(CloudFile file, int fileSiz } } - protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) throws StorageException, IOException, URISyntaxException { + protected static void doDownloadRangeToByteArrayNegativeTests(CloudFile file) throws StorageException, IOException { int fileLength = 1024; int resultBufSize = 1024; final Random randGenerator = new Random(); 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 f82d3a3..655afc4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -439,12 +439,7 @@ public static class HeaderConstants { * The blob sequence number equal condition header. */ public static final String IF_SEQUENCE_NUMBER_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-eq"; - - /** - * Specifies snapshots are to be included. - */ - public static final String INCLUDE_SNAPSHOTS_VALUE = "include"; - + /** * The header that specifies the lease action to perform */ @@ -580,7 +575,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2016-10-16"; + public static final String TARGET_STORAGE_VERSION = "2016-05-31"; /** * The header that specifies the next visible time for a queue message. @@ -752,11 +747,6 @@ public static class QueryConstants { */ public static final String SNAPSHOT = "snapshot"; - /** - * The query component for snapshot time. - */ - public static final String SHARE_SNAPSHOT = "sharesnapshot"; - /** * The query component for the SAS start partition key. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java index e30fe93..214ec54 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageErrorCodeStrings.java @@ -564,11 +564,6 @@ public final class StorageErrorCodeStrings { */ public static final String SHARE_DISABLED = "ShareDisabled"; - /** - * The specified share contains snapshots. - */ - public static final String SHARE_HAS_SNAPSHOTS = "ShareHasSnapshots"; - /** * The specified share was not found. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index f2d7fd8..2e33407 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -156,6 +156,11 @@ final class BlobConstants { */ public static final int DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES = 32 * Constants.MB; + /** + * Specifies snapshots are to be included. + */ + public static final String INCLUDE_SNAPSHOTS_VALUE = "include"; + /** * XML element for the latest. */ 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 cfb8244..904bfbd 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 @@ -373,7 +373,7 @@ public static HttpURLConnection deleteBlob(final URI uri, final BlobRequestOptio break; case INCLUDE_SNAPSHOTS: request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, - HeaderConstants.INCLUDE_SNAPSHOTS_VALUE); + BlobConstants.INCLUDE_SNAPSHOTS_VALUE); break; case DELETE_SNAPSHOTS_ONLY: request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java index 24fd758..e5ca53f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SR.java @@ -91,7 +91,6 @@ public class SR { public static final String INVALID_MIME_RESPONSE = "Invalid MIME response received."; public static final String INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER = "Page data must be a multiple of 512 bytes. Buffer currently contains %d bytes."; public static final String INVALID_OPERATION_FOR_A_SNAPSHOT = "Cannot perform this operation on a blob representing a snapshot."; - public static final String INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT = "Cannot perform this operation on a share representing a snapshot."; public static final String INVALID_PAGE_BLOB_LENGTH = "Page blob length must be multiple of 512."; public static final String INVALID_PAGE_START_OFFSET = "Page start offset must be multiple of 512."; public static final String INVALID_RANGE_CONTENT_MD5_HEADER = "Cannot specify x-ms-range-get-content-md5 header on ranges larger than 4 MB. Either use a BlobReadStream via openRead, or disable TransactionalMD5 via the BlobRequestOptions."; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java index dc3167d..1fd78ad 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/SharedAccessSignatureHelper.java @@ -418,7 +418,7 @@ public static StorageCredentialsSharedAccessSignature parseQuery(final StorageUr */ public static StorageCredentialsSharedAccessSignature parseQuery(final HashMap queryParams) throws StorageException { - + boolean sasParameterFound = false; List removeList = new ArrayList(); for (final Entry entry : queryParams.entrySet()) { @@ -434,11 +434,9 @@ public static StorageCredentialsSharedAccessSignature parseQuery(final HashMapCloudFile class using the specified absolute URI * and credentials. @@ -154,9 +154,8 @@ public CloudFile(final StorageUri fileAbsoluteUri) throws StorageException, URIS * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials) throws StorageException, URISyntaxException { + public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { this(new StorageUri(fileAbsoluteUri), credentials); } @@ -171,9 +170,8 @@ public CloudFile(final URI fileAbsoluteUri, final StorageCredentials credentials * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - public CloudFile(final StorageUri fileAbsoluteUri, final StorageCredentials credentials) throws StorageException, URISyntaxException { + public CloudFile(final StorageUri fileAbsoluteUri, final StorageCredentials credentials) throws StorageException { this.parseQueryAndVerify(fileAbsoluteUri, credentials); } @@ -231,10 +229,9 @@ protected CloudFile(final StorageUri uri, final String fileName, final CloudFile * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public final void abortCopy(final String copyId) throws StorageException, URISyntaxException { + public final void abortCopy(final String copyId) throws StorageException { this.abortCopy(copyId, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -256,17 +253,14 @@ public final void abortCopy(final String copyId) throws StorageException, URISyn * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public final void abortCopy(final String copyId, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -432,10 +426,9 @@ public final String startCopy(final CloudFile sourceFile, final AccessCondition * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public final String startCopy(final URI source) throws StorageException, URISyntaxException { + public final String startCopy(final URI source) throws StorageException { return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */, null /* options */, null /* opContext */); } @@ -463,19 +456,16 @@ public final String startCopy(final URI source) throws StorageException, URISynt * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException * */ @DoesServiceRequest public final String startCopy(final URI source, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, URISyntaxException { + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -544,10 +534,9 @@ public String preProcessResponse(CloudFile file, CloudFileClient client, Operati * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void clearRange(final long offset, final long length) throws StorageException, URISyntaxException { + public void clearRange(final long offset, final long length) throws StorageException { this.clearRange(offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -574,17 +563,14 @@ public void clearRange(final long offset, final long length) throws StorageExcep * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void clearRange(final long offset, final long length, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { + FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); @@ -601,10 +587,9 @@ public void clearRange(final long offset, final long length, final AccessConditi * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void create(final long size) throws StorageException, URISyntaxException { + public void create(final long size) throws StorageException { this.create(size, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -627,18 +612,15 @@ public void create(final long size) throws StorageException, URISyntaxException * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void create(final long size, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); ExecutionEngine.executeWithRetry(this.fileServiceClient, this, this.createImpl(size, accessCondition, options), @@ -690,10 +672,9 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public final void delete() throws StorageException, URISyntaxException { + public final void delete() throws StorageException { this.delete(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -713,17 +694,14 @@ public final void delete() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public final void delete(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -738,11 +716,10 @@ public final void delete(final AccessCondition accessCondition, FileRequestOptio * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException * */ @DoesServiceRequest - public final boolean deleteIfExists() throws StorageException, URISyntaxException { + public final boolean deleteIfExists() throws StorageException { return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -764,13 +741,11 @@ public final boolean deleteIfExists() throws StorageException, URISyntaxExceptio * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public final boolean deleteIfExists(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - this.getShare().assertNoSnapshot(); boolean exists = this.exists(true, accessCondition, options, opContext); if (exists) { @@ -1289,7 +1264,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, OperationContext context) throws Exception { return FileRequest.getFileRanges(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, file.getShare().snapshotID); + options, context, accessCondition); } @Override @@ -1356,7 +1331,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op // : accessCondition; return FileRequest.getFile(file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, file.getShare().snapshotID, this.getOffset(), this.getLength(), + options, context, accessCondition, this.getOffset(), this.getLength(), (options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated())); } @@ -1531,7 +1506,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op throws Exception { return FileRequest.getFileProperties( file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, file.getShare().snapshotID); + options, context, accessCondition); } @Override @@ -1628,7 +1603,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFile file, Op throws Exception { return FileRequest.getFileProperties( file.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, file.getShare().snapshotID); + options, context, accessCondition); } @Override @@ -1850,10 +1825,9 @@ public final FileInputStream openRead(final AccessCondition accessCondition, Fil * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public FileOutputStream openWriteExisting() throws StorageException, URISyntaxException { + public FileOutputStream openWriteExisting() throws StorageException { return this .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -1877,7 +1851,6 @@ public FileOutputStream openWriteExisting() throws StorageException, URISyntaxEx * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileRequestOptions options, @@ -1900,10 +1873,9 @@ public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileR * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public FileOutputStream openWriteNew(final long length) throws StorageException, URISyntaxException { + public FileOutputStream openWriteNew(final long length) throws StorageException { return this .openOutputStreamInternal(length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -1933,11 +1905,10 @@ public FileOutputStream openWriteNew(final long length) throws StorageException, * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public FileOutputStream openWriteNew(final long length, AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { + FileRequestOptions options, OperationContext opContext) throws StorageException { return openOutputStreamInternal(length, accessCondition, options, opContext); } @@ -1964,16 +1935,13 @@ public FileOutputStream openWriteNew(final long length, AccessCondition accessCo * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { + FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient, false /* setStartTime */); if (length != null) { @@ -2008,10 +1976,9 @@ private FileOutputStream openOutputStreamInternal(Long length, AccessCondition a * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ public void uploadFromByteArray(final byte[] buffer, final int offset, final int length) throws StorageException, - IOException, URISyntaxException { + IOException { uploadFromByteArray(buffer, offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2038,11 +2005,10 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ public void uploadFromByteArray(final byte[] buffer, final int offset, final int length, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, IOException, URISyntaxException { + throws StorageException, IOException { ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length); this.upload(inputStream, length, accessCondition, options, opContext); inputStream.close(); @@ -2057,9 +2023,8 @@ public void uploadFromByteArray(final byte[] buffer, final int offset, final int * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ - public void uploadFromFile(final String path) throws StorageException, IOException, URISyntaxException { + public void uploadFromFile(final String path) throws StorageException, IOException { uploadFromFile(path, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2082,10 +2047,9 @@ public void uploadFromFile(final String path) throws StorageException, IOExcepti * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ public void uploadFromFile(final String path, final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, IOException, URISyntaxException { + OperationContext opContext) throws StorageException, IOException { File file = new File(path); long fileLength = file.length(); InputStream inputStream = new BufferedInputStream(new java.io.FileInputStream(file)); @@ -2103,9 +2067,8 @@ public void uploadFromFile(final String path, final AccessCondition accessCondit * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ - public void uploadText(final String content) throws StorageException, IOException, URISyntaxException { + public void uploadText(final String content) throws StorageException, IOException { this.uploadText(content, null /* charsetName */, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2132,10 +2095,9 @@ public void uploadText(final String content) throws StorageException, IOExceptio * @throws StorageException * If a storage service error occurred. * @throws IOException - * @throws URISyntaxException */ public void uploadText(final String content, final String charsetName, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, IOException, URISyntaxException { + FileRequestOptions options, OperationContext opContext) throws StorageException, IOException { byte[] bytes = (charsetName == null) ? content.getBytes() : content.getBytes(charsetName); this.uploadFromByteArray(bytes, 0, bytes.length, accessCondition, options, opContext); } @@ -2155,11 +2117,10 @@ public void uploadText(final String content, final String charsetName, final Acc * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void uploadRange(final InputStream sourceStream, final long offset, final long length) - throws StorageException, IOException, URISyntaxException { + throws StorageException, IOException { this.uploadRange(sourceStream, offset, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2188,18 +2149,15 @@ public void uploadRange(final InputStream sourceStream, final long offset, final * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void uploadRange(final InputStream sourceStream, final long offset, final long length, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, IOException, URISyntaxException { + throws StorageException, IOException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); final FileRange range = new FileRange(offset, offset + length - 1); @@ -2327,10 +2285,9 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public final void uploadMetadata() throws StorageException, URISyntaxException { + public final void uploadMetadata() throws StorageException { this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2354,18 +2311,15 @@ public final void uploadMetadata() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public final void uploadMetadata(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2421,10 +2375,9 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public final void uploadProperties() throws StorageException, URISyntaxException { + public final void uploadProperties() throws StorageException { this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */); } @@ -2447,17 +2400,14 @@ public final void uploadProperties() throws StorageException, URISyntaxException * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public final void uploadProperties(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2509,9 +2459,8 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ - public void resize(long size) throws StorageException, URISyntaxException { + public void resize(long size) throws StorageException { this.resize(size, null /* accessCondition */, null /* options */, null /* operationContext */); } @@ -2533,16 +2482,13 @@ public void resize(long size) throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ public void resize(long size, AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -2597,10 +2543,9 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException, URISyntaxException { + public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException { this.upload(sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */); } @@ -2628,17 +2573,14 @@ public void upload(final InputStream sourceStream, final long length) throws Sto * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void upload(final InputStream sourceStream, final long length, final AccessCondition accessCondition, - FileRequestOptions options, OperationContext opContext) throws StorageException, IOException, URISyntaxException { + FileRequestOptions options, OperationContext opContext) throws StorageException, IOException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); if (length < 0) { @@ -2727,10 +2669,9 @@ protected static String getParentNameFromURI(final StorageUri resourceAddress, f * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) - throws StorageException, URISyntaxException { + throws StorageException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { @@ -2738,7 +2679,7 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -2755,13 +2696,6 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } - - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - - final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); - if (snapshotIDs != null && snapshotIDs.length > 0) { - this.getShare().snapshotID = snapshotIDs[0]; - } } protected void updateEtagAndLastModifiedFromResponse(HttpURLConnection request) { @@ -2798,8 +2732,7 @@ public final CloudFileShare getShare() throws StorageException, URISyntaxExcepti if (this.share == null) { final StorageUri shareUri = PathUtility.getShareURI(this.getStorageUri(), this.fileServiceClient.isUsePathStyleUris()); - - this.share = new CloudFileShare(shareUri, this.fileServiceClient.getCredentials(), null); + this.share = new CloudFileShare(shareUri, this.fileServiceClient.getCredentials()); } return this.share; 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 030fa41..f5bac52 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 @@ -17,7 +17,6 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; -import java.util.EnumSet; import com.microsoft.azure.storage.DoesServiceRequest; import com.microsoft.azure.storage.OperationContext; @@ -101,29 +100,7 @@ public CloudFileClient(StorageUri storageUri, StorageCredentials credentials) { */ public CloudFileShare getShareReference(final String shareName) throws URISyntaxException, StorageException { Utility.assertNotNullOrEmpty("shareName", shareName); - return this.getShareReference(shareName, null); - } - - /** - * Gets a {@link CloudFileShare} object with the specified name. - * - * @param shareName - * The name of the share, which must adhere to share naming rules. The share name should not - * include any path separator characters (/). - * Share names must be lowercase, between 3-63 characters long and must start with a letter or - * number. Share names may contain only letters, numbers, and the dash (-) character. - * @param snapshotID - * A String that represents the snapshot ID of the share. - * @return A reference to a {@link CloudFileShare} object. - * @throws StorageException - * @throws URISyntaxException - * - * @see Naming and Referencing Shares, - * Directories, Files, and Metadata - */ - public CloudFileShare getShareReference(final String shareName, String snapshotID) throws URISyntaxException, StorageException { - Utility.assertNotNullOrEmpty("shareName", shareName); - return new CloudFileShare(shareName, snapshotID, this); + return new CloudFileShare(shareName, this); } /** @@ -134,7 +111,7 @@ public CloudFileShare getShareReference(final String shareName, String snapshotI */ @DoesServiceRequest public Iterable listShares() { - return this.listSharesWithPrefix(null, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */); + return this.listSharesWithPrefix(null, ShareListingDetails.NONE, null /* options */, null /* opContext */); } /** @@ -149,7 +126,7 @@ public Iterable listShares() { */ @DoesServiceRequest public Iterable listShares(final String prefix) { - return this.listSharesWithPrefix(prefix, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */); + return this.listSharesWithPrefix(prefix, ShareListingDetails.NONE, null /* options */, null /* opContext */); } /** @@ -159,8 +136,7 @@ public Iterable listShares(final String prefix) { * @param prefix * A String that represents the share name prefix. * @param detailsIncluded - * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate - * whether share snapshots and/or metadata will be returned. + * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. * @param options * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying * null will use the default request options from the associated service client ( @@ -174,7 +150,7 @@ public Iterable listShares(final String prefix) { * shares for this client. */ @DoesServiceRequest - public Iterable listShares(final String prefix, final EnumSet detailsIncluded, + public Iterable listShares(final String prefix, final ShareListingDetails detailsIncluded, final FileRequestOptions options, final OperationContext opContext) { return this.listSharesWithPrefix(prefix, detailsIncluded, options, opContext); } @@ -190,7 +166,7 @@ public Iterable listShares(final String prefix, final EnumSet listSharesSegmented() throws StorageException { - return this.listSharesSegmented(null, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */, + return this.listSharesSegmented(null, ShareListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -210,7 +186,7 @@ public ResultSegment listSharesSegmented() throws StorageExcepti */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix) throws StorageException { - return this.listSharesWithPrefixSegmented(prefix, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */, + return this.listSharesWithPrefixSegmented(prefix, ShareListingDetails.NONE, null, null /* continuationToken */, null /* options */, null /* opContext */); } @@ -221,8 +197,7 @@ public ResultSegment listSharesSegmented(final String prefix) th * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate - * whether share snapshots and/or metadata will be returned. + * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. * @param maxResults * The maximum number of results to retrieve. If null or greater * than 5000, the server will return up to 5,000 items. Must be at least 1. @@ -246,7 +221,7 @@ public ResultSegment listSharesSegmented(final String prefix) th */ @DoesServiceRequest public ResultSegment listSharesSegmented(final String prefix, - final EnumSet detailsIncluded, final Integer maxResults, + final ShareListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, final FileRequestOptions options, final OperationContext opContext) throws StorageException { return this.listSharesWithPrefixSegmented(prefix, detailsIncluded, maxResults, continuationToken, options, @@ -260,8 +235,7 @@ public ResultSegment listSharesSegmented(final String prefix, * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A java.util.EnumSet object that contains {@link ShareListingDetails} - * values that indicate whether snapshots and/or metadata are returned. + * A {@link ShareListingDetails} value that indicates whether share metadata will be returned. * @param options * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying * null will use the default request options from the associated service client ( @@ -275,7 +249,7 @@ public ResultSegment listSharesSegmented(final String prefix, * shares whose names begin with the specified prefix. */ private Iterable listSharesWithPrefix(final String prefix, - final EnumSet detailsIncluded, FileRequestOptions options, OperationContext opContext) { + final ShareListingDetails detailsIncluded, FileRequestOptions options, OperationContext opContext) { if (opContext == null) { opContext = new OperationContext(); } @@ -297,8 +271,7 @@ private Iterable listSharesWithPrefix(final String prefix, * @param prefix * A String that represents the prefix of the share name. * @param detailsIncluded - * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate - * whether share snapshots and/or metadata will be returned. + * A {@link FileListingDetails} value that indicates whether share metadata will be returned. * @param maxResults * The maximum number of results to retrieve. If null or greater * than 5000, the server will return up to 5,000 items. Must be at least 1. @@ -321,7 +294,7 @@ private Iterable listSharesWithPrefix(final String prefix, * If a storage service error occurred. */ private ResultSegment listSharesWithPrefixSegmented(final String prefix, - final EnumSet detailsIncluded, final Integer maxResults, + final ShareListingDetails detailsIncluded, final Integer maxResults, final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -342,7 +315,7 @@ private ResultSegment listSharesWithPrefixSegmented(final String } private StorageRequest> listSharesWithPrefixSegmentedImpl( - final String prefix, final EnumSet detailsIncluded, final Integer maxResults, + final String prefix, final ShareListingDetails detailsIncluded, final Integer maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) { Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.SHARE); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java index 92735f7..a479446 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java @@ -94,9 +94,8 @@ public final class CloudFileDirectory implements ListFileItem { * @param directoryAbsoluteUri * A {@link URI} that represents the file directory's address. * @throws StorageException - * @throws URISyntaxException */ - public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException, URISyntaxException { + public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException { this(new StorageUri(directoryAbsoluteUri)); } @@ -106,9 +105,8 @@ public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageExceptio * @param directoryAbsoluteUri * A {@link StorageUri} that represents the file directory's address. * @throws StorageException - * @throws URISyntaxException */ - public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException, URISyntaxException { + public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException { this(directoryAbsoluteUri, (StorageCredentials) null); } @@ -121,10 +119,9 @@ public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageE * @param credentials * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException - * @throws URISyntaxException */ public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentials credentials) - throws StorageException, URISyntaxException { + throws StorageException { this(new StorageUri(directoryAbsoluteUri), credentials); } @@ -137,10 +134,9 @@ public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentia * @param credentials * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException - * @throws URISyntaxException */ public CloudFileDirectory(final StorageUri directoryAbsoluteUri, final StorageCredentials credentials) - throws StorageException, URISyntaxException { + throws StorageException { this.parseQueryAndVerify(directoryAbsoluteUri, credentials); } @@ -171,10 +167,9 @@ protected CloudFileDirectory(final StorageUri uri, final String directoryName, f * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void create() throws StorageException, URISyntaxException { + public void create() throws StorageException { this.create(null /* options */, null /* opContext */); } @@ -192,16 +187,13 @@ public void create() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void create(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { + public void create(FileRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -258,10 +250,9 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public boolean createIfNotExists() throws StorageException, URISyntaxException { + public boolean createIfNotExists() throws StorageException { return this.createIfNotExists(null /* options */, null /* opContext */); } @@ -281,14 +272,11 @@ public boolean createIfNotExists() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { + public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - this.getShare().assertNoSnapshot(); - boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext); if (exists) { return false; @@ -315,10 +303,9 @@ public boolean createIfNotExists(FileRequestOptions options, OperationContext op * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void delete() throws StorageException, URISyntaxException { + public void delete() throws StorageException { this.delete(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -338,17 +325,14 @@ public void delete() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, URISyntaxException { + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -394,10 +378,9 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public boolean deleteIfExists() throws StorageException, URISyntaxException { + public boolean deleteIfExists() throws StorageException { return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -419,11 +402,10 @@ public boolean deleteIfExists() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); @@ -516,7 +498,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory OperationContext context) throws Exception { return FileRequest.getDirectoryProperties( directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, - accessCondition, directory.getShare().snapshotID); + accessCondition); } @Override @@ -529,12 +511,7 @@ public void signRequest(HttpURLConnection connection, CloudFileClient client, Op public Boolean preProcessResponse(CloudFileDirectory directory, CloudFileClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { - // Set properties - final FileDirectoryAttributes attributes = - FileResponse.getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris()); - directory.setMetadata(attributes.getMetadata()); - directory.setProperties(attributes.getProperties()); - + directory.updatePropertiesFromResponse(this.getConnection()); return Boolean.valueOf(true); } else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { @@ -569,10 +546,9 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest - public void uploadMetadata() throws StorageException, URISyntaxException { + public void uploadMetadata() throws StorageException { this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */); } @@ -592,17 +568,14 @@ public void uploadMetadata() throws StorageException, URISyntaxException { * * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ @DoesServiceRequest public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException, URISyntaxException { + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } - this.getShare().assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -706,7 +679,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory OperationContext context) throws Exception { return FileRequest.getDirectoryProperties( directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, - accessCondition, directory.getShare().snapshotID); + accessCondition); } @Override @@ -927,7 +900,7 @@ public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory .getNextMarker() : null); return FileRequest.listFilesAndDirectories( directory.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, listingContext, directory.getShare().snapshotID); + options, context, listingContext); } @Override @@ -1085,9 +1058,7 @@ public CloudFileDirectory getParent() throws URISyntaxException, StorageExceptio if (parentName != null) { StorageUri parentURI = PathUtility.appendPathToUri(this.getShare().getStorageUri(), parentName); - this.parent = new CloudFileDirectory(parentURI, this.getServiceClient().getCredentials()); - //this.parent = new CloudFileDirectory(parentURI, parentName, this.getShare()); } } return this.parent; @@ -1164,10 +1135,9 @@ protected void setStorageUri(final StorageUri storageUri) { * A {@link StorageCredentials} object used to authenticate access. * @throws StorageException * If a storage service error occurred. - * @throws URISyntaxException */ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials) - throws StorageException, URISyntaxException { + throws StorageException { Utility.assertNotNull("completeUri", completeUri); if (!completeUri.isAbsolute()) { @@ -1175,7 +1145,7 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -1192,13 +1162,6 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } - - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - - final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); - if (snapshotIDs != null && snapshotIDs.length > 0) { - this.getShare().snapshotID = snapshotIDs[0]; - } } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java index 399b787..318b949 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileShare.java @@ -76,11 +76,6 @@ public final class CloudFileShare { */ private StorageUri storageUri; - /** - * Holds the snapshot ID. - */ - String snapshotID; - /** * Holds a reference to the associated service client. */ @@ -95,8 +90,6 @@ public final class CloudFileShare { * The share name should not include any path separator characters (/). * Share names must be lowercase, between 3-63 characters long and must start with a letter or * number. Share names may contain only letters, numbers, and the dash (-) character. - * @param snapshotID - * A String that represents the snapshot version, if applicable. * @param client * A {@link CloudFileClient} object that represents the associated service client, and that specifies the * endpoint for the File service. @@ -109,14 +102,13 @@ public final class CloudFileShare { * @see Naming and Referencing Shares, * Directories, Files, and Metadata */ - protected CloudFileShare(final String shareName, String snapshotID, final CloudFileClient client) throws URISyntaxException, + protected CloudFileShare(final String shareName, final CloudFileClient client) throws URISyntaxException, StorageException { Utility.assertNotNull("client", client); Utility.assertNotNull("shareName", shareName); this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), shareName); this.name = shareName; - this.snapshotID = snapshotID; this.fileServiceClient = client; } @@ -143,9 +135,9 @@ public CloudFileShare(final URI uri) throws StorageException { * If a storage service error occurred. */ public CloudFileShare(final StorageUri storageUri) throws StorageException { - this(storageUri, (StorageCredentials) null, null); + this(storageUri, (StorageCredentials) null); } - + /** * Creates an instance of the CloudFileShare class using the specified URI and credentials. * @@ -153,14 +145,12 @@ public CloudFileShare(final StorageUri storageUri) throws StorageException { * A java.net.URI object that represents the absolute URI of the share. * @param credentials * A {@link StorageCredentials} object used to authenticate access. - * @param snapshotID - * A String that represents the snapshot version, if applicable. * * @throws StorageException * If a storage service error occurred. */ - public CloudFileShare(final URI uri, final StorageCredentials credentials, String snapshotID) throws StorageException { - this(new StorageUri(uri), credentials, snapshotID); + public CloudFileShare(final URI uri, final StorageCredentials credentials) throws StorageException { + this(new StorageUri(uri), credentials); } /** @@ -170,22 +160,12 @@ public CloudFileShare(final URI uri, final StorageCredentials credentials, Strin * A {@link StorageUri} object which represents the absolute StorageUri of the share. * @param credentials * A {@link StorageCredentials} object used to authenticate access. - * @param snapshotID - * A String that represents the snapshot version, if applicable. + * * @throws StorageException * If a storage service error occurred. */ - public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials, String snapshotID) throws StorageException { + public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials) throws StorageException { this.parseQueryAndVerify(storageUri, credentials); - - if (snapshotID != null) { - if (this.snapshotID != null) { - throw new IllegalArgumentException(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED); - } - else { - this.snapshotID = snapshotID; - } - } } /** @@ -220,11 +200,6 @@ public void create(FileRequestOptions options, OperationContext opContext) throw opContext = new OperationContext(); } - assertNoSnapshot(); - if (this.properties != null && this.properties.getShareQuota() != null) { - Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA); - } - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -342,7 +317,7 @@ public boolean createIfNotExists(FileRequestOptions options, OperationContext op */ @DoesServiceRequest public void delete() throws StorageException { - this.delete(DeleteShareSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */); + this.delete(null /* accessCondition */, null /* options */, null /* opContext */); } /** @@ -365,36 +340,6 @@ public void delete() throws StorageException { @DoesServiceRequest public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - this.delete(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext); - } - - /** - * Deletes the share using the specified snapshot and request options, and operation context. - *

- * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use - * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value - * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share. - * - * @param deleteSnapshotsOption - * A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share - * and its snapshots. - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the share. - * @param options - * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudFileClient}). - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - public void delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -402,12 +347,12 @@ public void delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondi opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(deleteSnapshotsOption, accessCondition, options), + ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest deleteImpl( - final DeleteShareSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition, final FileRequestOptions options) { + final AccessCondition accessCondition, final FileRequestOptions options) { final StorageRequest putRequest = new StorageRequest(options, this.getStorageUri()) { @@ -416,7 +361,7 @@ private StorageRequest deleteImpl( public HttpURLConnection buildRequest( CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.deleteShare( - share.getTransformedAddress().getPrimaryUri(), options, context, accessCondition, share.snapshotID, deleteSnapshotsOption); + share.getTransformedAddress().getPrimaryUri(), options, context, accessCondition); } @Override @@ -449,13 +394,12 @@ public Void preProcessResponse(CloudFileShare share, CloudFileClient client, Ope */ @DoesServiceRequest public boolean deleteIfExists() throws StorageException { - return this.deleteIfExists(DeleteShareSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */); + return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */); } - + /** * Deletes the share if it exists using the specified request options and operation context. * - * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. * @param options @@ -475,45 +419,12 @@ public boolean deleteIfExists() throws StorageException { @DoesServiceRequest public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - return this.deleteIfExists(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext); - - } - - /** - * Deletes the share if it exists, using the specified snapshot and request options, and operation context. - *

- * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use - * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value - * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share. - * - * @param deleteSnapshotsOption - * A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share - * and its snapshots. - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the share. - * @param options - * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudFileClient}). - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return true if the share existed and was deleted; otherwise, false. - * - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - public boolean deleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext); if (exists) { try { - this.delete(deleteSnapshotsOption, accessCondition, options, opContext); + this.delete(accessCondition, options, opContext); return true; } catch (StorageException e) { @@ -588,7 +499,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, accessCondition, share.snapshotID); + options, context, accessCondition); } @Override @@ -656,8 +567,6 @@ public FileSharePermissions downloadPermissions(AccessCondition accessCondition, opContext = new OperationContext(); } - assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -717,136 +626,6 @@ public FileSharePermissions postProcessResponse(HttpURLConnection connection, return getRequest; } - /** - * Creates a snapshot of the share. - * - * @return A CloudFileShare object that represents the snapshot of the share. - * - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - public final CloudFileShare createSnapshot() throws StorageException { - return this - .createSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* opContext */); - } - - /** - * Creates a snapshot of the file share using the specified request options and operation context. - * - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the share. - * @param options - * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudFileClient}). - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A CloudFileShare object that represents the snapshot of the file share. - * - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - public final CloudFileShare createSnapshot(final AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { - return this.createSnapshot(null /* metadata */, accessCondition, options, opContext); - } - - /** - * Creates a snapshot of the file share using the specified request options and operation context. - * - * @param metadata - * A collection of name-value pairs defining the metadata of the snapshot, or null. - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the file share. - * @param options - * A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying - * null will use the default request options from the associated service client ( - * {@link CloudFileClient}). - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A CloudFileShare object that represents the snapshot of the file share. - * - * @throws StorageException - * If a storage service error occurred. - */ - @DoesServiceRequest - public final CloudFileShare createSnapshot(final HashMap metadata, - final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) - throws StorageException { - assertNoSnapshot(); - - if (opContext == null) { - opContext = new OperationContext(); - } - - opContext.initialize(); - options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); - - return ExecutionEngine - .executeWithRetry(this.fileServiceClient, this, - this.createSnapshotImpl(metadata, accessCondition, options), options.getRetryPolicyFactory(), - opContext); - } - - private StorageRequest createSnapshotImpl( - final HashMap metadata, final AccessCondition accessCondition, - final FileRequestOptions options) { - final StorageRequest putRequest = - new StorageRequest( - options, this.getStorageUri()) { - - @Override - public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) - throws Exception { - return FileRequest.snapshotShare(share.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, accessCondition); - } - - @Override - public void setHeaders(HttpURLConnection connection, CloudFileShare share, OperationContext context) { - if (metadata != null) { - FileRequest.addMetadata(connection, metadata, context); - } - } - - @Override - public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context) - throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); - } - - @Override - public CloudFileShare preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) - throws Exception { - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { - this.setNonExceptionedRetryableFailure(true); - return null; - } - - final String snapshotTime = FileResponse.getSnapshotTime(this.getConnection()); - CloudFileShare snapshot = new CloudFileShare(share.getName(), snapshotTime, client); - snapshot.setProperties(new FileShareProperties(share.properties)); - - // use the specified metadata if not null : otherwise share's metadata - snapshot.setMetadata(metadata != null ? metadata : share.metadata); - - snapshot.updatePropertiesFromResponse(this.getConnection()); - - return snapshot; - } - }; - - return putRequest; - } - /** * Queries the service for this share's {@link ShareStats}. * @@ -883,8 +662,6 @@ public ShareStats getStats(FileRequestOptions options, OperationContext opContex opContext = new OperationContext(); } - assertNoSnapshot(); - opContext.initialize(); options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient); @@ -1004,7 +781,7 @@ public void setRequestLocationMode() { public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception { return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()), - options, context, accessCondition, share.snapshotID); + options, context, accessCondition); } @Override @@ -1017,11 +794,7 @@ public void signRequest(HttpURLConnection connection, CloudFileClient client, Op public Boolean preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { - final FileShareAttributes attributes = FileResponse.getFileShareAttributes(this.getConnection(), - client.isUsePathStyleUris()); - share.metadata = attributes.getMetadata(); - share.properties = attributes.getProperties(); - + share.updatePropertiesFromResponse(this.getConnection()); return Boolean.valueOf(true); } else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { @@ -1029,7 +802,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { } else { this.setNonExceptionedRetryableFailure(true); - // return false instead of null to avoid SCA issues return false; } @@ -1040,10 +812,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { } private void updatePropertiesFromResponse(HttpURLConnection request) { - if (this.getProperties() == null) { - this.properties = new FileShareProperties(); - } - // ETag this.getProperties().setEtag(request.getHeaderField(Constants.HeaderConstants.ETAG)); @@ -1056,15 +824,6 @@ private void updatePropertiesFromResponse(HttpURLConnection request) { } } - /** - * Asserts that the share is not a snapshot. - */ - protected void assertNoSnapshot() { - if (isSnapshot()) { - throw new IllegalArgumentException(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT); - } - } - /** * Returns a shared access signature for the share. Note this does not contain the leading "?". * @@ -1172,8 +931,6 @@ public void uploadMetadata() throws StorageException { @DoesServiceRequest public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - assertNoSnapshot(); - if (opContext == null) { opContext = new OperationContext(); } @@ -1262,12 +1019,6 @@ public final void uploadProperties() throws StorageException { public final void uploadProperties( AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - assertNoSnapshot(); - - if (this.properties != null && this.properties.getShareQuota() != null) { - Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA); - } - if (opContext == null) { opContext = new OperationContext(); } @@ -1349,8 +1100,6 @@ public void uploadPermissions(final FileSharePermissions permissions) throws Sto @DoesServiceRequest public void uploadPermissions(final FileSharePermissions permissions, final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext) throws StorageException { - assertNoSnapshot(); - if (opContext == null) { opContext = new OperationContext(); } @@ -1451,14 +1200,7 @@ private void parseQueryAndVerify(final StorageUri completeUri, final StorageCred } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); - - final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); - - final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT); - if (snapshotIDs != null && snapshotIDs.length > 0) { - this.snapshotID = snapshotIDs[0]; - } - + final StorageCredentialsSharedAccessSignature parsedCredentials = SharedAccessSignatureHelper.parseQuery(completeUri); @@ -1495,26 +1237,6 @@ public URI getUri() { return this.storageUri.getPrimaryUri(); } - /** - * Returns the snapshotID for this share. - * - * @return The snapshotID as a string for this share. - */ - public final String getSnapshot() { - return this.snapshotID; - } - - /** - * Indicates whether this share is a snapshot. - * - * @return true if the share is a snapshot, otherwise false. - * - * @see DeleteSnapshotsOption - */ - public final boolean isSnapshot() { - return this.snapshotID != null; - } - /** * Returns the list of URIs for all locations. * @@ -1524,24 +1246,6 @@ public StorageUri getStorageUri() { return this.storageUri; } - /** - * Returns the snapshot or shared access signature qualified URI for this share. - * - * @return A java.net.URI object that represents the snapshot or shared access signature. - * - * @throws StorageException - * If a storage service error occurred. - * @throws URISyntaxException - * If the resource URI is invalid. - */ - public final URI getQualifiedUri() throws URISyntaxException, StorageException { - if (this.isSnapshot()) { - return PathUtility.addToQuery(this.getUri(), String.format("sharesnapshot=%s", this.snapshotID)); - } - - return this.fileServiceClient.getCredentials().transformUri(this.getUri()); - } - /** * Returns the name of the share. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java deleted file mode 100644 index c44b495..0000000 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/DeleteShareSnapshotsOption.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Microsoft Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * - */ -package com.microsoft.azure.storage.file; - -/** - * Specifies options when calling the delete share operation. - */ -public enum DeleteShareSnapshotsOption { - - /** - * Specifies deleting the blob and its snapshots. - */ - INCLUDE_SNAPSHOTS, - - /** - * Specifies deleting the blob only. If the blob has snapshots, this option will result in an error from the - * service. - */ - NONE -} 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 7ab437c..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 @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URISyntaxException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.Callable; @@ -206,8 +205,6 @@ public void close() throws IOException { } catch (final StorageException e) { throw Utility.initIOException(e); - } catch (URISyntaxException e) { - throw Utility.initIOException(e); } } finally { @@ -230,10 +227,9 @@ public void close() throws IOException { * * @throws StorageException * An exception representing any error which occurred during the operation. - * @throws URISyntaxException */ @DoesServiceRequest - private void commit() throws StorageException, URISyntaxException { + private void commit() throws StorageException { if (this.options.getStoreFileContentMD5()) { this.parentFileRef.getProperties().setContentMD5(Base64.encode(this.md5Digest.digest())); } @@ -275,7 +271,7 @@ private synchronized void dispatchWrite(final int writeLength) throws IOExceptio worker = new Callable() { @Override - public Void call() throws URISyntaxException { + public Void call() { try { fileRef.uploadRange(bufferRef, opOffset, opWriteLength, FileOutputStream.this.accessCondition, FileOutputStream.this.options, FileOutputStream.this.opContext); 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 5489208..c119ce8 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileRequest.java @@ -18,7 +18,6 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; -import java.util.EnumSet; import java.util.Map; import com.microsoft.azure.storage.AccessCondition; @@ -39,8 +38,6 @@ final class FileRequest { private static final String RANGE_LIST_QUERY_ELEMENT_NAME = "rangelist"; - private static final String SNAPSHOTS_QUERY_ELEMENT_NAME = "snapshots"; - /** * Generates a web request to abort a copy operation. * @@ -121,23 +118,6 @@ private static void addProperties(final HttpURLConnection request, FilePropertie BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_TYPE_HEADER, properties.getContentType()); } - /** - * Adds the share snapshot if present. - * Only for listing files and directories which requires a different query param. - * - * @param builder - * a query builder. - * @param snapshotVersion - * the share snapshot version to the query builder. - * @throws StorageException - */ - public static void addShareSnapshot(final UriQueryBuilder builder, final String snapshotVersion) - throws StorageException { - if (snapshotVersion != null) { - builder.add(Constants.QueryConstants.SHARE_SNAPSHOT, snapshotVersion); - } - } - /** * Creates a request to copy a file, Sign with 0 length. * @@ -288,27 +268,14 @@ public static HttpURLConnection deleteFile(final URI uri, final FileRequestOptio * @throws IllegalArgumentException */ public static HttpURLConnection deleteShare(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition, String snapshotVersion, DeleteShareSnapshotsOption deleteSnapshotsOption) - throws IOException, URISyntaxException, StorageException { + final OperationContext opContext, final AccessCondition accessCondition) throws IOException, + URISyntaxException, StorageException { final UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); - FileRequest.addShareSnapshot(shareBuilder, snapshotVersion); HttpURLConnection request = BaseRequest.delete(uri, fileOptions, shareBuilder, opContext); if (accessCondition != null) { accessCondition.applyConditionToRequest(request); } - switch (deleteSnapshotsOption) { - case NONE: - // nop - break; - case INCLUDE_SNAPSHOTS: - request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER, - Constants.HeaderConstants.INCLUDE_SNAPSHOTS_VALUE); - break; - default: - break; - } - return request; } @@ -362,8 +329,6 @@ public static HttpURLConnection getAcl(final URI uri, final FileRequestOptions f * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. - * @param snapshotVersion - * The snapshot version, if the share is a snapshot. * @param offset * The offset at which to begin returning content. * @param count @@ -380,7 +345,7 @@ public static HttpURLConnection getAcl(final URI uri, final FileRequestOptions f * @throws IllegalArgumentException */ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion, final Long offset, + final OperationContext opContext, final AccessCondition accessCondition, final Long offset, final Long count, boolean requestRangeContentMD5) throws IOException, URISyntaxException, StorageException { if (offset != null && requestRangeContentMD5) { @@ -389,7 +354,6 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions } final UriQueryBuilder builder = new UriQueryBuilder(); - FileRequest.addShareSnapshot(builder, snapshotVersion); final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); request.setRequestMethod(Constants.HTTP_GET); @@ -434,8 +398,6 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. * @return a HttpURLConnection to use to perform the operation. - * @param snapshotVersion - * the snapshot version to the query builder. * @throws IOException * if there is an error opening the connection * @throws URISyntaxException @@ -445,11 +407,10 @@ public static HttpURLConnection getFile(final URI uri, final FileRequestOptions * @throws IllegalArgumentException */ public static HttpURLConnection getFileProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException, + final OperationContext opContext, final AccessCondition accessCondition) throws StorageException, IOException, URISyntaxException { final UriQueryBuilder builder = new UriQueryBuilder(); - - return getProperties(uri, fileOptions, opContext, accessCondition, builder, snapshotVersion); + return getProperties(uri, fileOptions, opContext, accessCondition, builder); } /** @@ -467,8 +428,6 @@ public static HttpURLConnection getFileProperties(final URI uri, final FileReque * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the file. - * @param snapshotVersion - * the snapshot version to the query builder. * @return a HttpURLConnection to use to perform the operation. * @throws IOException * if there is an error opening the connection @@ -479,11 +438,10 @@ public static HttpURLConnection getFileProperties(final URI uri, final FileReque * @throws IllegalArgumentException */ public static HttpURLConnection getFileRanges(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException, + final OperationContext opContext, final AccessCondition accessCondition) throws StorageException, IOException, URISyntaxException { final UriQueryBuilder builder = new UriQueryBuilder(); - addShareSnapshot(builder, snapshotVersion); builder.add(Constants.QueryConstants.COMPONENT, RANGE_LIST_QUERY_ELEMENT_NAME); final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); @@ -511,17 +469,14 @@ public static HttpURLConnection getFileRanges(final URI uri, final FileRequestOp * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. - * @param snapshotVersion - * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ public static HttpURLConnection getShareProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition, final String snapshotVersion) throws IOException, URISyntaxException, + final OperationContext opContext, AccessCondition accessCondition) throws IOException, URISyntaxException, StorageException { final UriQueryBuilder shareBuilder = getShareUriQueryBuilder(); - - return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder, snapshotVersion); + return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder); } /** @@ -604,16 +559,12 @@ private static UriQueryBuilder getDirectoryUriQueryBuilder() throws StorageExcep * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the share. - * @param snapshotVersion - * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ private static HttpURLConnection getProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition, final UriQueryBuilder builder, - String snapshotVersion) + final OperationContext opContext, AccessCondition accessCondition, final UriQueryBuilder builder) throws IOException, URISyntaxException, StorageException { - addShareSnapshot(builder, snapshotVersion); HttpURLConnection request = BaseRequest.getProperties(uri, fileOptions, builder, opContext); if (accessCondition != null) { @@ -640,8 +591,7 @@ private static HttpURLConnection getProperties(final URI uri, final FileRequestO * @param listingContext * A set of parameters for the listing operation. * @param detailsIncluded - * A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate - * whether share snapshots and/or metadata will be returned. + * Additional details to return with the listing. * @return a HttpURLConnection configured for the operation. * @throws IOException * @throws URISyntaxException @@ -650,28 +600,12 @@ private static HttpURLConnection getProperties(final URI uri, final FileRequestO */ public static HttpURLConnection listShares(final URI uri, final FileRequestOptions fileOptions, final OperationContext opContext, final ListingContext listingContext, - final EnumSet detailsIncluded) throws URISyntaxException, IOException, StorageException { - final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); + final ShareListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException { - if (detailsIncluded != null && detailsIncluded.size() > 0) { - final StringBuilder sb = new StringBuilder(); - boolean started = false; - - if (detailsIncluded.contains(ShareListingDetails.SNAPSHOTS)) { - started = true; - sb.append(SNAPSHOTS_QUERY_ELEMENT_NAME); - } - - if (detailsIncluded.contains(ShareListingDetails.METADATA)) { - if (started) - { - sb.append(","); - } - - sb.append(Constants.QueryConstants.METADATA); - } + final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext); - builder.add(Constants.QueryConstants.INCLUDE, sb.toString()); + if (detailsIncluded == ShareListingDetails.ALL || detailsIncluded == ShareListingDetails.METADATA) { + builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA); } final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); @@ -807,16 +741,14 @@ public static HttpURLConnection deleteDirectory(final URI uri, final FileRequest * the operation. * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the directory. - * @param snapshotVersion - * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws StorageException * */ public static HttpURLConnection getDirectoryProperties(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, AccessCondition accessCondition, String snapshotVersion) throws IOException, URISyntaxException, + final OperationContext opContext, AccessCondition accessCondition) throws IOException, URISyntaxException, StorageException { final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder(); - return getProperties(uri, fileOptions, opContext, accessCondition, directoryBuilder, snapshotVersion); + return getProperties(uri, fileOptions, opContext, accessCondition, directoryBuilder); } /** @@ -835,8 +767,6 @@ public static HttpURLConnection getDirectoryProperties(final URI uri, final File * the operation. * @param listingContext * A set of parameters for the listing operation. - * @param snapshotVersion - * the snapshot version to the query builder. * @return a HttpURLConnection configured for the operation. * @throws IOException * @throws URISyntaxException @@ -844,11 +774,10 @@ public static HttpURLConnection getDirectoryProperties(final URI uri, final File * @throws IllegalArgumentException */ public static HttpURLConnection listFilesAndDirectories(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final ListingContext listingContext, String snapshotVersion) throws URISyntaxException, + final OperationContext opContext, final ListingContext listingContext) throws URISyntaxException, IOException, StorageException { final UriQueryBuilder builder = getDirectoryUriQueryBuilder(); - addShareSnapshot(builder, snapshotVersion); builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST); if (listingContext != null) { @@ -1170,49 +1099,6 @@ public static HttpURLConnection setFileMetadata(final URI uri, final FileRequest return setMetadata(uri, fileOptions, opContext, accessCondition, null); } - /** - * Constructs a HttpURLConnection to create a snapshot of the share. - * - * @param uri - * A java.net.URI object that specifies the absolute URI. - * @param fileOptions - * A {@link FileRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify null to use the request options specified on the - * {@link CloudFileClient}. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * @param accessCondition - * An {@link AccessCondition} object that represents the access conditions for the share. - * @return a HttpURLConnection to use to perform the operation. - * @throws IOException - * if there is an error opening the connection - * @throws URISyntaxException - * if the resource URI is invalid - * @throws StorageException - * an exception representing any error which occurred during the operation. - * @throws IllegalArgumentException - */ - public static HttpURLConnection snapshotShare(final URI uri, final FileRequestOptions fileOptions, - final OperationContext opContext, final AccessCondition accessCondition) throws IOException, - URISyntaxException, StorageException { - final UriQueryBuilder builder = new UriQueryBuilder(); - builder.add(Constants.QueryConstants.RESOURCETYPE, "share"); - builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.SNAPSHOT); - final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext); - - request.setFixedLengthStreamingMode(0); - request.setDoOutput(true); - request.setRequestMethod(Constants.HTTP_PUT); - - if (accessCondition != null) { - accessCondition.applyConditionToRequest(request); - } - - return request; - } - /** * Constructs a HttpURLConnection to set the file's properties, Sign with zero length specified. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java index 14d7bcf..40a1c51 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java @@ -205,17 +205,6 @@ static Integer parseShareQuota(final HttpURLConnection request) { return (shareQuota == -1) ? null : shareQuota; } - /** - * Gets the snapshot ID from the request header. - * - * @param request - * The response from server. - * @return the snapshot ID from the request header. - */ - public static String getSnapshotTime(final HttpURLConnection request) { - return request.getHeaderField(Constants.HeaderConstants.SNAPSHOT_ID_HEADER); - } - /** * Private Default Ctor */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java index e834085..c5e29c6 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileShareProperties.java @@ -17,6 +17,7 @@ import java.util.Date; import com.microsoft.azure.storage.AccessCondition; +import com.microsoft.azure.storage.core.Utility; /** * Represents the system properties for a share. @@ -38,27 +39,6 @@ public final class FileShareProperties { */ private Integer shareQuota; - /** - * Creates an instance of the FileShareProperties class. - */ - public FileShareProperties() { - } - - /** - * Creates an instance of the FileShareProperties class by copying values from another - * FileShareProperties instance. - * - * @param other - * A {@link FileShareProperties} object which represents the file share properties to copy. - */ - public FileShareProperties(final FileShareProperties other) { - if (other != null) { - this.setEtag(other.getEtag()); - this.setLastModified(other.getLastModified()); - this.setShareQuota(other.getShareQuota()); - } - } - /** * Gets the ETag value of the share. *

@@ -122,6 +102,9 @@ protected void setLastModified(final Date lastModified) { * the size of files stored on the share. */ public void setShareQuota(Integer shareQuota) { + if (shareQuota != null) { + Utility.assertInBounds("Share Quota", shareQuota, 1, FileConstants.MAX_SHARE_QUOTA); + } this.shareQuota = shareQuota; } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java index 3d3b9b9..c73693d 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListHandler.java @@ -46,7 +46,6 @@ final class ShareListHandler extends DefaultHandler { private final ListResponse response = new ListResponse(); private FileShareAttributes attributes; private String shareName; - private String snapshotID; private ShareListHandler(CloudFileClient serviceClient) { this.serviceClient = serviceClient; @@ -78,7 +77,6 @@ public void startElement(String uri, String localName, String qName, Attributes if (FileConstants.SHARE_ELEMENT.equals(localName)) { this.shareName = Constants.EMPTY_STRING; - this.snapshotID = null; this.attributes = new FileShareAttributes(); } } @@ -107,7 +105,6 @@ public void endElement(String uri, String localName, String qName) throws SAXExc CloudFileShare retShare = this.serviceClient.getShareReference(this.shareName); retShare.setMetadata(this.attributes.getMetadata()); retShare.setProperties(this.attributes.getProperties()); - retShare.snapshotID = this.snapshotID; this.response.getResults().add(retShare); } @@ -137,9 +134,6 @@ else if (FileConstants.SHARE_ELEMENT.equals(parentNode)) { if (Constants.NAME_ELEMENT.equals(currentNode)) { this.shareName = value; } - else if (Constants.QueryConstants.SNAPSHOT.equals(currentNode.toLowerCase())) { - this.snapshotID = value; - } } else if (Constants.PROPERTIES.equals(parentNode)) { try { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java index 244b11f..b9b54dd 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/ShareListingDetails.java @@ -18,6 +18,10 @@ * Specifies which details to include when listing the shares in this storage account. */ public enum ShareListingDetails { + /** + * Specifies including all available details. + */ + ALL(1), /** * Specifies including share metadata. @@ -25,9 +29,9 @@ public enum ShareListingDetails { METADATA(1), /** - * Specifies listing share snapshots. + * Specifies including no additional details. */ - SNAPSHOTS(2); + NONE(0); /** * Returns the value of this enum. From 8211f15aeaf2e8c2ecaad07a2fee0f88a506ae34 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Sun, 28 May 2017 10:23:28 -0700 Subject: [PATCH 06/30] Update build tools version --- build.gradle | 3 ++- gradle/wrapper/gradle-wrapper.properties | 4 ++-- microsoft-azure-storage-samples/build.gradle | 2 +- microsoft-azure-storage-test/build.gradle | 2 +- microsoft-azure-storage/build.gradle | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 9b557d4..e5e86c1 100644 --- a/build.gradle +++ b/build.gradle @@ -17,9 +17,10 @@ buildscript { repositories { mavenCentral() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.2' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0822398..2e2aaa6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Nov 30 14:47:51 PST 2016 +#Sun May 28 10:18:41 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/microsoft-azure-storage-samples/build.gradle b/microsoft-azure-storage-samples/build.gradle index e97c5ad..ecbeddc 100644 --- a/microsoft-azure-storage-samples/build.gradle +++ b/microsoft-azure-storage-samples/build.gradle @@ -22,7 +22,7 @@ dependencies { android { compileSdkVersion 19 - buildToolsVersion "24.0.1" + buildToolsVersion '25.0.0' sourceSets { main { diff --git a/microsoft-azure-storage-test/build.gradle b/microsoft-azure-storage-test/build.gradle index 3652326..6b2ff5e 100644 --- a/microsoft-azure-storage-test/build.gradle +++ b/microsoft-azure-storage-test/build.gradle @@ -28,7 +28,7 @@ repositories { android { compileSdkVersion 19 - buildToolsVersion "24.0.1" + buildToolsVersion '25.0.0' defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/microsoft-azure-storage/build.gradle b/microsoft-azure-storage/build.gradle index d34279e..7920d86 100644 --- a/microsoft-azure-storage/build.gradle +++ b/microsoft-azure-storage/build.gradle @@ -22,7 +22,7 @@ dependencies { android { compileSdkVersion 19 - buildToolsVersion "24.0.1" + buildToolsVersion '25.0.0' sourceSets { main { From ec463536f855922ec142d0edb2ed9d7cfe22c5aa Mon Sep 17 00:00:00 2001 From: Santiago Castro Date: Mon, 17 Apr 2017 01:18:38 -0300 Subject: [PATCH 07/30] Fix broken Markdown headings --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1235654..acb8e95 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -#Microsoft Azure Storage SDK for Android +# Microsoft Azure Storage SDK for Android This project provides a client library for Android that makes it easy to consume Microsoft Azure Storage services. For documentation please see the [AndroidDocs](http://azure.github.io/azure-storage-android/). > If you are looking for the Azure Storage Java SDK, please visit [https://github.com/Azure/azure-storage-java](https://github.com/Azure/azure-storage-java). -#Features +# Features * Blob * Create/Read/Update/Delete containers * Create/Read/Update/Delete blobs @@ -19,21 +19,21 @@ This project provides a client library for Android that makes it easy to consume * Batch operations * Advanced Table Operations -#Getting Started +# Getting Started -##Download -###Option 1: Source Zip +## Download +### Option 1: Source Zip To download a copy of the source code, click "Download ZIP" on the right side of the page or click [here](https://github.com/Azure/azure-storage-android/archive/master.zip). Unzip and navigate to the microsoft-azure-storage folder. -###Option 2: Source via Git +### Option 2: Source Via Git To get the source code of the SDK via git just type: git clone git://github.com/Azure/azure-storage-android.git cd ./azure-storage-android/microsoft-azure-storage -###Option 3: aar via Gradle +### Option 3: aar via Gradle To get the binaries of this library as distributed by Microsoft, ready for use within your project, you can use Gradle. @@ -49,7 +49,7 @@ Then, add a dependency by adding the following to your gradle build file: compile 'com.microsoft.azure.android:azure-storage-android:1.0.0@aar' } -###Option 4: aar via Maven +### Option 4: aar via Maven To get the binaries of this library as distributed by Microsoft, ready for use within your project, you can use Maven. @@ -62,14 +62,14 @@ To get the binaries of this library as distributed by Microsoft, ready for use w ``` -##Minimum Requirements and Setup +## Minimum Requirements and Setup * [Jackson-Core](https://github.com/FasterXML/jackson-core) is used for JSON parsing. * Android 4.0/15+ * (Optional) Gradle or Maven This library is currently tested to work on Android versions 4.0+. Compatibility with older versions is not guaranteed. -##Usage +## Usage To use this SDK to call Microsoft Azure storage services, you need to first [create an account](https://account.windowsazure.com/signup). @@ -79,21 +79,21 @@ Make sure the storage client library is added as a project dependency. From Andr If using Maven or Gradle, Jackson-Core should be automatically added to the build path. Otherwise, please download the jar and add it to your build path. Also, please make sure that the jar will be added to your project's apk. To do this in Android Studio, go to File -> Project Structure -> Modules. Click the Dependencies tab. Click the '+' sign and click 'File Dependency'. Navigate the .jar. -##Code Samples +## Code Samples Runnable samples for blob, queue, and table may be found in the microsoft-azure-storage-samples directory. To run these samples, specify a connection string in the MainActivity class and add a dependency on the Android client library. For additional information on using the Android client library, the Java [general documentation](http://azure.microsoft.com/en-us/develop/java/) and Java How To guides for [blobs](http://azure.microsoft.com/en-us/documentation/articles/storage-java-how-to-use-blob-storage/), [queues](http://azure.microsoft.com/en-us/documentation/articles/storage-java-how-to-use-queue-storage/), [tables](http://azure.microsoft.com/en-us/documentation/articles/storage-java-how-to-use-table-storage/) may be helpful. -#Need Help? +# Need Help? Be sure to check out the Azure [Developer Forums on MSDN](http://social.msdn.microsoft.com/Forums/windowsazure/en-US/home?forum=windowsazuredata) or the [Developer Forums on Stack Overflow](http://stackoverflow.com/questions/tagged/azure+windows-azure-storage) if you have trouble with the provided code. -#Contribute Code or Provide Feedback +# Contribute Code or Provide Feedback If you would like to become an active contributor to this project please follow the instructions provided in [Azure Projects Contribution Guidelines](http://azure.github.io/guidelines/). If you encounter any bugs with the library please file an issue in the [Issues](https://github.com/Azure/azure-storage-android/issues) section of the project. -#Learn More +# Learn More * [Azure Storage Service](http://azure.microsoft.com/en-us/documentation/services/storage/) * [Azure Storage Team Blog](http://blogs.msdn.com/b/windowsazurestorage/) * [AndroidDocs](http://azure.github.io/azure-storage-android/) From 363af07557054448f065bf8b9d060cfe54c6925e Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 25 Apr 2017 12:36:26 -0700 Subject: [PATCH 08/30] Cleaning up merge from public dev --- .../src/com/microsoft/azure/storage/file/CloudFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index 7d41cc4..106f3e8 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1854,7 +1854,7 @@ public FileOutputStream openWriteExisting() throws StorageException { */ @DoesServiceRequest public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException { + OperationContext opContext) throws StorageException, URISyntaxException { return this.openOutputStreamInternal(null /* length */, accessCondition, options, opContext); } From f40b38f690ec792bb9dcc5c85be70daf125e47f0 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Thu, 27 Apr 2017 14:48:40 -0700 Subject: [PATCH 09/30] Remove check that MD5 is sent by service --- .../storage/blob/CloudBlockBlobTests.java | 24 +++++++++++++++++++ .../azure/storage/blob/CloudBlob.java | 6 ----- 2 files changed, 24 insertions(+), 6 deletions(-) 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 a45ba9e..d6eebf4 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 @@ -22,6 +22,7 @@ import com.microsoft.azure.storage.SendingRequestEvent; import com.microsoft.azure.storage.StorageCredentialsAnonymous; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestRunners.CloudTests; @@ -1029,6 +1030,29 @@ public void testBlobUploadWithoutMD5Validation() throws URISyntaxException, Stor assertEquals("MDAwMDAwMDA=", blockBlobRef2.properties.getContentMD5()); } + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testVerifyTransactionalMD5ValidationMissingOverallMD5() throws URISyntaxException, StorageException, IOException { + final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob"); + final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName); + + final int length = 2 * 1024 * 1024; + ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length); + BlobRequestOptions options = new BlobRequestOptions(); + options.setSingleBlobPutThresholdInBytes(1024*1024); + options.setDisableContentMD5Validation(true); + options.setStoreBlobContentMD5(false); + + blockBlobRef.upload(srcStream, -1, null, options, null); + + options.setDisableContentMD5Validation(false); + options.setStoreBlobContentMD5(true); + options.setUseTransactionalContentMD5(true); + final CloudBlockBlob blockBlobRef2 = this.container.getBlockBlobReference(blockBlobName); + blockBlobRef2.downloadRange(1024, (long)1024, new ByteArrayOutputStream(), null, options, null); + assertNull(blockBlobRef2.getProperties().getContentMD5()); + } + @Test @Category({ DevFabricTests.class, DevStoreTests.class }) public void testBlockBlobUploadContentMD5() throws URISyntaxException, StorageException, IOException { 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 e2a6fc4..df4bf54 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 @@ -1305,12 +1305,6 @@ public Integer preProcessResponse(CloudBlob blob, CloudBlobClient client, Operat final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(), blob.getStorageUri(), blob.snapshotID); - if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5() - && Utility.isNullOrEmpty(retrievedAttributes.getProperties().getContentMD5())) { - throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5, - Constants.HeaderConstants.HTTP_UNUSED_306, null, null); - } - blob.properties = retrievedAttributes.getProperties(); blob.metadata = retrievedAttributes.getMetadata(); From 0e7c8e7053f15d5efc3c6a138fa3fc85333a4b24 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 2 May 2017 18:46:27 -0700 Subject: [PATCH 10/30] Fixed transactional MD5 check --- .../src/com/microsoft/azure/storage/blob/CloudBlob.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 df4bf54..dcaaecb 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 @@ -1311,6 +1311,13 @@ public Integer preProcessResponse(CloudBlob blob, CloudBlobClient client, Operat // Need to store the Content MD5 in case we fail part way through. // We would still need to verify the entire range. String contentMD5 = this.getConnection().getHeaderField(Constants.HeaderConstants.CONTENT_MD5); + + if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5() + && Utility.isNullOrEmpty(contentMD5)) { + throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5, + Constants.HeaderConstants.HTTP_UNUSED_306, null, null); + } + this.setContentMD5(contentMD5); this.setLockedETag(blob.properties.getEtag()); this.setArePropertiesPopulated(true); From d7938a70239d1eec98d58a3a833a002f8dfb69e4 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Thu, 4 May 2017 09:40:18 -0700 Subject: [PATCH 11/30] Fixed transactional MD5 check for files --- ChangeLog.txt | 2 ++ .../azure/storage/file/CloudFileTests.java | 22 +++++++++++++++++++ .../azure/storage/file/CloudFile.java | 12 +++++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index f1488a3..755f95a 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,8 @@ 2017.XX.XX Version 1.1.0 * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. + * Fixed a bug where an exception was being thrown on a range get of a blob or file when the options disableContentMD5Validation is set to false and useTransactionalContentMD5 is set to true and there is no overall MD5. + * Fixed a bug where retries were happening immediately if a sock exception was thrown. 2017.01.30 Version 1.0.0 * Android Studio support via Gradle build system. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java index 1556854..e30bcdb 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileTests.java @@ -1108,6 +1108,28 @@ public void testCloudFileUploadRange() throws URISyntaxException, StorageExcepti inputStream = new ByteArrayInputStream(buffer); } + + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testVerifyTransactionalMD5ValidationMissingOverallMD5() throws URISyntaxException, StorageException, IOException { + final String fileName = FileTestHelper.generateRandomFileName(); + final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName); + + final int length = 3*1024; + ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length); + FileRequestOptions options = new FileRequestOptions(); + options.setDisableContentMD5Validation(true); + options.setStoreFileContentMD5(false); + + fileRef.upload(srcStream, length, null, options, null); + + options.setDisableContentMD5Validation(false); + options.setStoreFileContentMD5(true); + options.setUseTransactionalContentMD5(true); + final CloudFile fileRef2 = this.share.getRootDirectoryReference().getFileReference(fileName); + fileRef2.downloadRange(1024, (long)1024, new ByteArrayOutputStream(), null, options, null); + assertNull(fileRef2.getProperties().getContentMD5()); + } /** * Test clearing file ranges. diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index 106f3e8..1356462 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1354,12 +1354,6 @@ public Integer preProcessResponse(CloudFile file, CloudFileClient client, Operat final FileAttributes retrievedAttributes = FileResponse.getFileAttributes(this.getConnection(), file.getStorageUri()); - if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5() - && Utility.isNullOrEmpty(retrievedAttributes.getProperties().getContentMD5())) { - throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5, - Constants.HeaderConstants.HTTP_UNUSED_306, null, null); - } - file.properties = retrievedAttributes.getProperties(); file.metadata = retrievedAttributes.getMetadata(); @@ -1367,6 +1361,12 @@ public Integer preProcessResponse(CloudFile file, CloudFileClient client, Operat // We would still need to verify the entire range. this.setContentMD5(this.getConnection().getHeaderField(Constants.HeaderConstants.CONTENT_MD5)); + if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5() + && Utility.isNullOrEmpty(this.getContentMD5())) { + throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5, + Constants.HeaderConstants.HTTP_UNUSED_306, null, null); + } + this.setLockedETag(file.properties.getEtag()); this.setArePropertiesPopulated(true); } From b365c2c0ba0debdab287cdd2af0bfffa3d1d8050 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 22 May 2017 16:34:46 -0700 Subject: [PATCH 12/30] Fix failing test which relied on small put blob size --- .../com/microsoft/azure/storage/MaximumExecutionTimeTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java index f2dd60d..7eecbc0 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/MaximumExecutionTimeTests.java @@ -192,6 +192,9 @@ public void testMaximumExecutionTimeBlobWrites() throws URISyntaxException, Stor BlobRequestOptions options = new BlobRequestOptions(); options.setMaximumExecutionTimeInMs(5000); + // set a lower put blob threshold so that we perform multiple put block requests that timeout + options.setSingleBlobPutThresholdInBytes(32 * Constants.MB); + CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference(generateRandomName("container")); From 3c4cf0289b9dabee8130608ae3ba5e6e359a7b11 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 30 May 2017 21:52:00 -0700 Subject: [PATCH 13/30] Cleaning up changes --- .../com/microsoft/azure/storage/blob/CloudBlockBlobTests.java | 1 - .../src/com/microsoft/azure/storage/file/CloudFile.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 d6eebf4..b4dd807 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 @@ -22,7 +22,6 @@ import com.microsoft.azure.storage.SendingRequestEvent; import com.microsoft.azure.storage.StorageCredentialsAnonymous; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; -import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestRunners.CloudTests; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index 1356462..a72c521 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1854,7 +1854,7 @@ public FileOutputStream openWriteExisting() throws StorageException { */ @DoesServiceRequest public FileOutputStream openWriteExisting(AccessCondition accessCondition, FileRequestOptions options, - OperationContext opContext) throws StorageException, URISyntaxException { + OperationContext opContext) throws StorageException { return this.openOutputStreamInternal(null /* length */, accessCondition, options, opContext); } From 22f8b63a06f8d6b8f1b9323ae9e235f61c425a70 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Thu, 13 Jul 2017 13:37:22 -0700 Subject: [PATCH 14/30] Minor fixes --- build.gradle | 2 +- microsoft-azure-storage-test/AndroidManifest.xml | 2 +- .../com/microsoft/azure/storage/blob/CloudBlockBlobTests.java | 1 - .../src/com/microsoft/azure/storage/file/CloudFile.java | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index e5e86c1..32b2dd7 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' } } diff --git a/microsoft-azure-storage-test/AndroidManifest.xml b/microsoft-azure-storage-test/AndroidManifest.xml index a9f47ef..c33af35 100644 --- a/microsoft-azure-storage-test/AndroidManifest.xml +++ b/microsoft-azure-storage-test/AndroidManifest.xml @@ -20,7 +20,7 @@ Date: Mon, 5 Jun 2017 16:32:02 -0700 Subject: [PATCH 15/30] Fix transactional MD5 validations on recovery --- ChangeLog.txt | 1 + .../azure/storage/blob/CloudBlob.java | 2 +- .../azure/storage/core/StorageRequest.java | 18 +++ .../microsoft/azure/storage/core/Utility.java | 119 +++++++++++++++--- .../azure/storage/file/CloudFile.java | 2 +- .../azure/storage/file/FileOutputStream.java | 2 +- 6 files changed, 126 insertions(+), 18 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 755f95a..6180c32 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,5 @@ 2017.XX.XX Version 1.1.0 + * Fixed a bug where the tranactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. * Fixed a bug where an exception was being thrown on a range get of a blob or file when the options disableContentMD5Validation is set to false and useTransactionalContentMD5 is set to true and there is no overall MD5. 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 dcaaecb..5717b7a 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 @@ -1345,7 +1345,7 @@ public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, // writeToOutputStream will update the currentRequestByteCount on this request in case a retry // is needed and download should resume from that point final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false, - validateMD5, context, options, this); + validateMD5, context, options, true, this, this.getCurrentDescriptor()); // length was already checked by the NetworkInputStream, now check Md5 if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) { 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 09d6ff4..bcd6bcb 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 @@ -99,6 +99,11 @@ public abstract class StorageRequest { */ private String contentMD5 = null; + /** + * Holds the descriptor which contains the stream length and MD5 hash. + */ + private StreamMd5AndLength currentDescriptor = null; + /** * Denotes the StorageUri of the request */ @@ -212,6 +217,11 @@ public final String getContentMD5() { return this.contentMD5; } + /** + * @return the current descriptor which contains the stream length and MD5 hash. + */ + protected StreamMd5AndLength getCurrentDescriptor() { return this.currentDescriptor; } + /** * @return the location mode used to decide which location the request should be sent to. */ @@ -457,6 +467,14 @@ public void setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; } + /** + * @param currentDescriptor + * the descriptor value + */ + protected void setCurrentDescriptor(StreamMd5AndLength currentDescriptor) { + this.currentDescriptor = currentDescriptor; + } + /** * @param etagLockCondition * the locked ETag condition 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 cbd9670..bad1c51 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 @@ -1088,7 +1088,81 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, final RequestOptions options) throws IOException, StorageException { return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext, - options, null /*StorageRequest*/); + options, true); + } + + /** + * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and + * optionally calculates the MD5 hash for the data. + * + * @param sourceStream + * An InputStream object that represents the input stream to use as the source. + * @param outStream + * An OutputStream object that represents the output stream to use as the destination. + * @param writeLength + * The number of bytes to read from the stream. + * @param rewindSourceStream + * true if the input stream should be rewound before it is read; otherwise, + * false + * @param calculateMD5 + * true if an MD5 hash will be calculated; otherwise, false. + * @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 options + * A {@link RequestOptions} object that specifies any additional options for the request. Namely, the + * maximum execution time. + * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash. + * + * @throws IOException + * If an I/O error occurs. + * @throws StorageException + * If a storage service error occurred. + */ + public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, + long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, + final RequestOptions options, final Boolean shouldFlush) throws IOException, StorageException { + return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext, + options, shouldFlush, null /*StorageRequest*/, null /* descriptor */); + } + + /** + * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and + * optionally calculates the MD5 hash for the data. + * + * @param sourceStream + * An InputStream object that represents the input stream to use as the source. + * @param outStream + * An OutputStream object that represents the output stream to use as the destination. + * @param writeLength + * The number of bytes to read from the stream. + * @param rewindSourceStream + * true if the input stream should be rewound before it is read; otherwise, + * false + * @param calculateMD5 + * true if an MD5 hash will be calculated; otherwise, false. + * @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 options + * A {@link RequestOptions} object that specifies any additional options for the request. Namely, the + * maximum execution time. + * @param request + * Used by download resume to set currentRequestByteCount on the request. Otherwise, null is always used. + * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash. + * + * @throws IOException + * If an I/O error occurs. + * @throws StorageException + * If a storage service error occurred. + */ + public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, + long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, + final RequestOptions options, final Boolean shouldFlush, StorageRequest request) + throws IOException, StorageException { + return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext, options, shouldFlush, request, null /* descriptor */); } /** @@ -1115,6 +1189,11 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr * maximum execution time. * @param request * Used by download resume to set currentRequestByteCount on the request. Otherwise, null is always used. + * @param descriptor + * A {@Link StreamMd5AndLength} object to append to in the case of recovery action or null if this is not called + * from a recovery. This value needs to be passed for recovery in case part of the body has already been read, + * the recovery will attempt to download the remaining bytes but will do MD5 validation on the originally + * requested range size. * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash. * * @throws IOException @@ -1124,23 +1203,32 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr */ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, +<<<<<<< HEAD final RequestOptions options, StorageRequest request) throws IOException, StorageException { +======= + final RequestOptions options, final Boolean shouldFlush, StorageRequest request, StreamMd5AndLength descriptor) + throws IOException, StorageException { +>>>>>>> 1c67198... Fix transactional MD5 validations on recovery if (rewindSourceStream && sourceStream.markSupported()) { sourceStream.reset(); sourceStream.mark(Constants.MAX_MARK_LENGTH); } - final StreamMd5AndLength retVal = new StreamMd5AndLength(); - - if (calculateMD5) { - try { - retVal.setDigest(MessageDigest.getInstance("MD5")); - } - catch (final NoSuchAlgorithmException e) { - // This wont happen, throw fatal. - throw Utility.generateNewUnexpectedStorageException(e); + if (descriptor == null) { + descriptor = new StreamMd5AndLength(); + if (calculateMD5) { + try { + descriptor.setDigest(MessageDigest.getInstance("MD5")); + } + catch (final NoSuchAlgorithmException e) { + // This wont happen, throw fatal. + throw Utility.generateNewUnexpectedStorageException(e); + } } } + else { + descriptor.setMd5(null); + } if (writeLength < 0) { writeLength = Long.MAX_VALUE; @@ -1164,17 +1252,18 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr } if (calculateMD5) { - retVal.getDigest().update(retrievedBuff, 0, count); + descriptor.getDigest().update(retrievedBuff, 0, count); } - retVal.setLength(retVal.getLength() + count); - retVal.setCurrentOperationByteCount(retVal.getCurrentOperationByteCount() + count); + descriptor.setLength(descriptor.getLength() + count); + descriptor.setCurrentOperationByteCount(descriptor.getCurrentOperationByteCount() + count); if (request != null) { request.setCurrentRequestByteCount(request.getCurrentRequestByteCount() + count); + request.setCurrentDescriptor(descriptor); } - nextCopy = (int) Math.min(retrievedBuff.length, writeLength - retVal.getLength()); + nextCopy = (int) Math.min(retrievedBuff.length, writeLength - descriptor.getLength()); count = sourceStream.read(retrievedBuff, 0, nextCopy); } @@ -1182,7 +1271,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr outStream.flush(); } - return retVal; + return descriptor; } /** diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index a72c521..b185daa 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1401,7 +1401,7 @@ public Integer postProcessResponse(HttpURLConnection connection, CloudFile file, // writeToOutputStream will update the currentRequestByteCount on this request in case a retry // is needed and download should resume from that point final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false, - validateMD5, context, options, this); + validateMD5, context, options, true, this, this.getCurrentDescriptor()); // length was already checked by the NetworkInputStream, now check Md5 if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) { 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 db6b41e..566ed03 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 @@ -400,7 +400,7 @@ public void write(final byte[] data, final int offset, final int length) throws */ @DoesServiceRequest public void write(final InputStream sourceStream, final long writeLength) throws IOException, StorageException { - Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options); + Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false, null /* descriptor */); } /** From 2b5c369ea0e5de43393b77c3d537367c986d8bf3 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 13 Jun 2017 14:04:00 -0700 Subject: [PATCH 16/30] Remove extra overload of writeToOutputStream --- ChangeLog.txt | 2 +- .../microsoft/azure/storage/core/Utility.java | 38 ------------------- .../azure/storage/file/FileOutputStream.java | 2 +- 3 files changed, 2 insertions(+), 40 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 6180c32..5864bee 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,5 @@ 2017.XX.XX Version 1.1.0 - * Fixed a bug where the tranactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. + * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. * Fixed a bug where an exception was being thrown on a range get of a blob or file when the options disableContentMD5Validation is set to false and useTransactionalContentMD5 is set to true and there is no overall MD5. 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 bad1c51..750cdfc 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 @@ -1127,44 +1127,6 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr options, shouldFlush, null /*StorageRequest*/, null /* descriptor */); } - /** - * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and - * optionally calculates the MD5 hash for the data. - * - * @param sourceStream - * An InputStream object that represents the input stream to use as the source. - * @param outStream - * An OutputStream object that represents the output stream to use as the destination. - * @param writeLength - * The number of bytes to read from the stream. - * @param rewindSourceStream - * true if the input stream should be rewound before it is read; otherwise, - * false - * @param calculateMD5 - * true if an MD5 hash will be calculated; otherwise, false. - * @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 options - * A {@link RequestOptions} object that specifies any additional options for the request. Namely, the - * maximum execution time. - * @param request - * Used by download resume to set currentRequestByteCount on the request. Otherwise, null is always used. - * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash. - * - * @throws IOException - * If an I/O error occurs. - * @throws StorageException - * If a storage service error occurred. - */ - public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, - long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, - final RequestOptions options, final Boolean shouldFlush, StorageRequest request) - throws IOException, StorageException { - return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext, options, shouldFlush, request, null /* descriptor */); - } - /** * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and * optionally calculates the MD5 hash for the data. 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 566ed03..83b660a 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 @@ -400,7 +400,7 @@ public void write(final byte[] data, final int offset, final int length) throws */ @DoesServiceRequest public void write(final InputStream sourceStream, final long writeLength) throws IOException, StorageException { - Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false, null /* descriptor */); + Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false, null /* request */, null /* descriptor */); } /** From ab3ece2489b3c16b70c87e05b97d6f82cb922014 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 13 Jun 2017 14:08:24 -0700 Subject: [PATCH 17/30] Reverting overload calls for streams --- .../src/com/microsoft/azure/storage/file/FileOutputStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 83b660a..f768db8 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 @@ -400,7 +400,7 @@ public void write(final byte[] data, final int offset, final int length) throws */ @DoesServiceRequest public void write(final InputStream sourceStream, final long writeLength) throws IOException, StorageException { - Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false, null /* request */, null /* descriptor */); + Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false); } /** From ecf4955e0e798ab1737ecc7771f1d01b6b49f348 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 11 Jul 2017 10:35:45 -0700 Subject: [PATCH 18/30] Premium Page Blob Tiers --- ChangeLog.txt | 2 + .../microsoft/azure/storage/TestHelper.java | 32 +- .../microsoft/azure/storage/TestRunners.java | 10 +- .../azure/storage/blob/BlobTestHelper.java | 8 + .../storage/blob/CloudPageBlobTests.java | 208 ++++++++++- .../microsoft/azure/storage/Constants.java | 9 +- .../azure/storage/blob/BlobConstants.java | 13 +- .../azure/storage/blob/BlobListHandler.java | 5 + .../azure/storage/blob/BlobProperties.java | 48 ++- .../azure/storage/blob/BlobRequest.java | 95 ++++- .../azure/storage/blob/BlobResponse.java | 20 ++ .../azure/storage/blob/CloudBlob.java | 49 ++- .../azure/storage/blob/CloudPageBlob.java | 326 ++++++++++++++++-- .../storage/blob/PremiumPageBlobTier.java | 112 ++++++ 14 files changed, 903 insertions(+), 34 deletions(-) create mode 100644 microsoft-azure-storage/src/com/microsoft/azure/storage/blob/PremiumPageBlobTier.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 5864bee..4b5b82d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,6 @@ 2017.XX.XX Version 1.1.0 + * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. + * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java index 415b550..a2dfca9 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java @@ -14,6 +14,12 @@ */ package com.microsoft.azure.storage; +<<<<<<< HEAD +======= +import static org.junit.Assert.*; +import static org.junit.Assume.assumeNotNull; + +>>>>>>> 44be010... Premium Page Blob Tiers import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; @@ -23,9 +29,28 @@ import java.util.Arrays; import java.util.Random; +<<<<<<< HEAD import junit.framework.Assert; import com.microsoft.azure.storage.SharedAccessAccountPolicy; +======= +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.AssumptionViolatedException; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.microsoft.azure.keyvault.extensions.RsaKey; +import com.microsoft.azure.keyvault.extensions.SymmetricKey; +>>>>>>> 44be010... Premium Page Blob Tiers import com.microsoft.azure.storage.analytics.CloudAnalyticsClient; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.file.CloudFileClient; @@ -58,6 +83,11 @@ public static CloudTableClient createCloudTableClient() throws StorageException return getAccount().createCloudTableClient(); } + public static CloudBlobClient createPremiumCloudBlobClient() throws StorageException { + CloudBlobClient client = getPremiumBlobAccount().createCloudBlobClient(); + return client; + } + public static CloudBlobClient createCloudBlobClient(SharedAccessAccountPolicy policy, boolean useHttps) throws StorageException, InvalidKeyException, URISyntaxException { @@ -241,7 +271,7 @@ public static URI defiddler(URI uri) throws URISyntaxException { return uri; } } - + public static void assertURIsEqual(URI expected, URI actual, boolean ignoreQueryOrder) { if (expected == null) { Assert.assertEquals(null, actual); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java index 901edf1..22c6023 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java @@ -104,6 +104,9 @@ public interface CloudTests { public interface DevFabricTests { } + public interface PremiumBlobTests { + } + // Test suites @RunWith(Suite.class) @SuiteClasses({AccountSasTests.class, EventFiringTests.class, GenericTests.class, MaximumExecutionTimeTests.class, @@ -115,7 +118,7 @@ public static class CoreTestSuite { @RunWith(Suite.class) @SuiteClasses({BlobOutputStreamTests.class, CloudBlobClientTests.class, CloudBlobContainerTests.class, CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class, - LeaseTests.class, SasTests.class}) + LeaseTests.class, SasTests.class, PremiumBlobTests.class}) public static class BlobTestSuite { } @@ -173,4 +176,9 @@ public static class DevFabricNoSecondarySuite { @SuiteClasses(AllTestSuite.class) public static class FastTestSuite { } + + @RunWith(Categories.class) + @IncludeCategory(PremiumBlobTests.class) + public static class PremiumBlobTestSuite { + } } \ No newline at end of file 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 480aa25..9daf49e 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 @@ -50,6 +50,14 @@ public static CloudBlobContainer getRandomContainerReference() throws URISyntaxE return container; } + public static CloudBlobContainer getRandomPremiumBlobContainerReference() throws URISyntaxException, StorageException { + String containerName = generateRandomContainerName(); + CloudBlobClient bClient = TestHelper.createPremiumCloudBlobClient(); + CloudBlobContainer container = bClient.getContainerReference(containerName); + + return container; + } + public static String generateRandomBlobNameWithPrefix(String prefix) { if (prefix == null) { prefix = ""; 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 4d1bd7c..a010453 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 @@ -14,8 +14,6 @@ */ package com.microsoft.azure.storage.blob; -import junit.framework.Assert; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -59,6 +57,7 @@ import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; +import com.microsoft.azure.storage.TestRunners.PremiumBlobTests; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.UriQueryBuilder; import com.microsoft.azure.storage.core.Utility; @@ -1200,4 +1199,209 @@ else if (overload == 2) { assertNotNull(copy.properties.getCopyState().getCopyDestinationSnapshotID()); assertNotNull(copy.getCopyState().getCompletionTime()); } + + @Test + public void testEightTBBlob() throws StorageException, URISyntaxException, IOException { + CloudPageBlob blob = this.container.getPageBlobReference("blob1"); + CloudPageBlob blob2 = this.container.getPageBlobReference("blob1"); + + long eightTb = 8L * 1024L * 1024L * 1024L * 1024L; + blob.create(eightTb); + assertEquals(eightTb, blob.getProperties().getLength()); + + blob2.downloadAttributes(); + assertEquals(eightTb, blob2.getProperties().getLength()); + + for (ListBlobItem listBlob : this.container.listBlobs()) { + CloudPageBlob listPageBlob = (CloudPageBlob)listBlob; + assertEquals(eightTb, listPageBlob.getProperties().getLength()); + } + + CloudPageBlob blob3 = this.container.getPageBlobReference("blob3"); + blob3.create(1024); + blob3.resize(eightTb); + + final Random randGenerator = new Random(); + final byte[] buffer = new byte[1024]; + randGenerator.nextBytes(buffer); + blob.uploadPages(new ByteArrayInputStream(buffer), eightTb - 512L, 512L); + + ArrayList ranges = blob.downloadPageRanges(); + assertEquals(1, ranges.size()); + assertEquals(eightTb - 512L, ranges.get(0).getStartOffset()); + assertEquals(eightTb - 1L, ranges.get(0).getEndOffset()); + } + + @Test + @Category(PremiumBlobTests.class) + public void testCloudPageBlobSetPremiumBlobTierOnCreate() throws URISyntaxException, StorageException, IOException { + CloudBlobContainer container = BlobTestHelper.getRandomPremiumBlobContainerReference(); + try { + container.create(); + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); + + // Test create API + CloudPageBlob blob = container.getPageBlobReference(blobName); + assertNull(blob.getProperties().getInferredBlobTier()); + blob.create(1024, PremiumPageBlobTier.P4, null, null, null); + assertEquals(PremiumPageBlobTier.P4, blob.getProperties().getPremiumPageBlobTier()); + assertFalse(blob.getProperties().getInferredBlobTier()); + + CloudPageBlob blob2 = container.getPageBlobReference(blobName); + blob2.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P4, blob2.getProperties().getPremiumPageBlobTier()); + assertNull(blob2.getProperties().getInferredBlobTier()); + + // Test upload from byte array API + byte[] buffer = BlobTestHelper.getRandomBuffer(1024); + CloudPageBlob blob3 = container.getPageBlobReference("blob3"); + blob3.uploadFromByteArray(buffer, 0, 1024, PremiumPageBlobTier.P6, null, null, null); + assertEquals(PremiumPageBlobTier.P6, blob3.getProperties().getPremiumPageBlobTier()); + assertFalse(blob3.getProperties().getInferredBlobTier()); + + CloudPageBlob blob3Ref = container.getPageBlobReference("blob3"); + blob3Ref.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P6, blob3Ref.getProperties().getPremiumPageBlobTier()); + assertNull(blob3Ref.getProperties().getInferredBlobTier()); + + // Test upload from stream API + ByteArrayInputStream srcStream = new ByteArrayInputStream(buffer); + CloudPageBlob blob4 = container.getPageBlobReference("blob4"); + blob4.upload(srcStream, 1024, PremiumPageBlobTier.P10, null, null, null); + assertEquals(PremiumPageBlobTier.P10, blob4.getProperties().getPremiumPageBlobTier()); + assertFalse(blob4.getProperties().getInferredBlobTier()); + + CloudPageBlob blob4Ref = container.getPageBlobReference("blob4"); + blob4Ref.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P10, blob4Ref.getProperties().getPremiumPageBlobTier()); + assertNull(blob4Ref.getProperties().getInferredBlobTier()); + + // Test upload from file API + File sourceFile = File.createTempFile("sourceFile", ".tmp"); + File destinationFile = new File(sourceFile.getParentFile(), + "destinationFile.tmp"); + FileOutputStream fos = new FileOutputStream(sourceFile); + fos.write(buffer); + fos.close(); + CloudPageBlob blob5 = container.getPageBlobReference("blob5"); + blob5.uploadFromFile(sourceFile.getAbsolutePath(), PremiumPageBlobTier.P20, null, null, null); + assertEquals(PremiumPageBlobTier.P20, blob5.getProperties().getPremiumPageBlobTier()); + assertFalse(blob5.getProperties().getInferredBlobTier()); + + CloudPageBlob blob5Ref = container.getPageBlobReference("blob5"); + blob5Ref.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P20, blob5Ref.getProperties().getPremiumPageBlobTier()); + assertNull(blob5Ref.getProperties().getInferredBlobTier()); + } + finally { + container.deleteIfExists(); + } + } + + @Test + @Category(PremiumBlobTests.class) + public void testCloudPageBlobSetBlobTier() throws URISyntaxException, StorageException { + CloudBlobContainer container = BlobTestHelper.getRandomPremiumBlobContainerReference(); + try { + container.create(); + String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); + CloudPageBlob blob = container.getPageBlobReference(blobName); + blob.create(1024); + assertNull(blob.getProperties().getInferredBlobTier()); + blob.downloadAttributes(); + assertTrue(blob.getProperties().getInferredBlobTier()); + assertEquals(PremiumPageBlobTier.P10, blob.getProperties().getPremiumPageBlobTier()); + + blob.uploadPremiumPageBlobTier(PremiumPageBlobTier.P40); + assertEquals(PremiumPageBlobTier.P40, blob.properties.getPremiumPageBlobTier()); + assertFalse(blob.getProperties().getInferredBlobTier()); + + CloudPageBlob blob2 = container.getPageBlobReference(blobName); + blob2.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P40, blob2.properties.getPremiumPageBlobTier()); + assertNull(blob2.getProperties().getInferredBlobTier()); + + boolean pageBlobWithTierFound = false; + for (ListBlobItem blobItem : container.listBlobs()) { + CloudPageBlob blob3 = (CloudPageBlob) blobItem; + + if (blob.getName().equals(blobName) && !pageBlobWithTierFound) { + // Check that the blob is found exactly once + assertEquals(PremiumPageBlobTier.P40, blob3.properties.getPremiumPageBlobTier()); + assertFalse(blob3.getProperties().getInferredBlobTier()); + pageBlobWithTierFound = true; + } else if (blob.getName().equals(blobName)) { + fail("Page blob found twice"); + } + } + + assertTrue(pageBlobWithTierFound); + + try + { + CloudPageBlob blob4 = container.getPageBlobReference("blob4"); + blob4.create(256 * (long)Constants.GB); + blob4.uploadPremiumPageBlobTier(PremiumPageBlobTier.P6); + fail("Expected failure when setting blob tier size to be less than content length"); + } + catch (StorageException e) + { + assertEquals("Specified blob tier size limit cannot be less than content length.", e.getMessage()); + } + + try + { + blob2.uploadPremiumPageBlobTier(PremiumPageBlobTier.P4); + fail("Expected failure when attempted to set the tier to a lower value than previously"); + } + catch (StorageException e) + { + assertEquals("A higher blob tier has already been explicitly set.", e.getMessage()); + } + } + finally { + container.deleteIfExists(); + } + } + + @Test + @Category(PremiumBlobTests.class) + public void testCloudPageBlobSetBlobTierOnCopy() throws URISyntaxException, StorageException, InterruptedException { + CloudBlobContainer container = BlobTestHelper.getRandomPremiumBlobContainerReference(); + try { + container.create(); + CloudPageBlob source = container.getPageBlobReference("source"); + source.create(1024, PremiumPageBlobTier.P10, null, null, null); + + // copy to larger disk + CloudPageBlob copy = container.getPageBlobReference("copy"); + String copyId = copy.startCopy(TestHelper.defiddler(source.getUri()), PremiumPageBlobTier.P30, null, null, null, null); + assertEquals(BlobType.PAGE_BLOB, copy.getProperties().getBlobType()); + assertEquals(PremiumPageBlobTier.P30, copy.getProperties().getPremiumPageBlobTier()); + assertEquals(PremiumPageBlobTier.P10, source.getProperties().getPremiumPageBlobTier()); + assertFalse(source.getProperties().getInferredBlobTier()); + assertFalse(copy.getProperties().getInferredBlobTier()); + BlobTestHelper.waitForCopy(copy); + + CloudPageBlob copyRef = container.getPageBlobReference("copy"); + copyRef.downloadAttributes(); + assertEquals(PremiumPageBlobTier.P30, copyRef.getProperties().getPremiumPageBlobTier()); + assertNull(copyRef.getProperties().getInferredBlobTier()); + + // copy where source does not have a tier + CloudPageBlob source2 = container.getPageBlobReference("source2"); + source2.create(1024); + + CloudPageBlob copy3 = container.getPageBlobReference("copy3"); + String copyId3 = copy3.startCopy(TestHelper.defiddler(source2.getUri()), PremiumPageBlobTier.P60, null ,null ,null, null); + assertEquals(BlobType.PAGE_BLOB, copy3.getProperties().getBlobType()); + assertEquals(PremiumPageBlobTier.P60, copy3.getProperties().getPremiumPageBlobTier()); + assertNull(source2.getProperties().getPremiumPageBlobTier()); + assertNull(source2.getProperties().getInferredBlobTier()); + assertFalse(copy3.getProperties().getInferredBlobTier()); + } + finally { + container.deleteIfExists(); + } + } } 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 655afc4..2f552c6 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -575,7 +575,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2016-05-31"; + public static final String TARGET_STORAGE_VERSION = "2017-04-17"; /** * The header that specifies the next visible time for a queue message. @@ -822,7 +822,12 @@ public static class QueryConstants { * XML element for an access policy. */ public static final String ACCESS_POLICY = "AccessPolicy"; - + + /** + * XML element for access tier. + */ + public static final String ACCESS_TIER = "AccessTier"; + /** * Buffer width used to copy data to output streams. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index 2e33407..f0ca820 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -20,12 +20,21 @@ * Holds the Constants used for the Blob Service. */ final class BlobConstants { - + + /** + * The header that specifies the access tier header. + */ + public static final String ACCESS_TIER_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "access-tier"; + + /** + * The header that specifies if the access tier is inferred. + */ + public static final String ACCESS_TIER_INFERRED_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "access-tier-inferred"; /** * Specifies the append blob type. */ public static final String APPEND_BLOB = "AppendBlob"; - + /** * XML element for authentication error details. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java index 7cb3f3b..1898af4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java @@ -324,5 +324,10 @@ else if (Constants.COPY_DESTINATION_SNAPSHOT_ID_ELEMENT.equals(currentNode)) { } this.copyState.setCopyDestinationSnapshotID(value); } + else if (Constants.ACCESS_TIER.equals(currentNode)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(value); + this.properties.setPremiumPageBlobTier(premiumPageBlobTier); + this.properties.setBlobTierInferredTier(false); + } } } \ No newline at end of file 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 b5519ac..0c227c4 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 @@ -117,6 +117,16 @@ public final class BlobProperties { */ private boolean isIncrementalCopy; + /** + * Represents the premium page blob tier. + */ + private PremiumPageBlobTier premiumPageBlobTier; + + /** + * Represents whether or not the blob tier is inferred. + */ + private Boolean isBlobTierInferredTier; + /** * Creates an instance of the BlobProperties class. */ @@ -150,6 +160,8 @@ public BlobProperties(final BlobProperties other) { this.pageBlobSequenceNumber = other.pageBlobSequenceNumber; this.serverEncrypted = other.serverEncrypted; this.isIncrementalCopy = other.isIncrementalCopy; + this.premiumPageBlobTier = other.premiumPageBlobTier; + this.isBlobTierInferredTier = other.isBlobTierInferredTier; } /** @@ -271,6 +283,13 @@ public Date getLastModified() { return this.lastModified; } + /** + * Gets a value indicating if the tier of the premium page blob has been inferred. + * + * @return A {@Link java.lang.Boolean} object which represents if the blob tier was inferred. + */ + public Boolean getInferredBlobTier() { return this.isBlobTierInferredTier; } + /** * Gets the lease status for the blob. * @@ -315,7 +334,16 @@ public long getLength() { public Long getPageBlobSequenceNumber() { return this.pageBlobSequenceNumber; } - + + /** + * If using a premium account and the blob is a page blob, gets the tier of the blob. + * @return A {@link PremiumPageBlobTier} object which represents the tier of the blob + * or null if the tier has not been set. + */ + public PremiumPageBlobTier getPremiumPageBlobTier() { + return this.premiumPageBlobTier; + } + /** * Gets the blob's server-side encryption status; * @@ -512,4 +540,22 @@ protected void setServerEncrypted(boolean serverEncrypted) { protected void setIncrementalCopy(boolean isIncrementalCopy) { this.isIncrementalCopy = isIncrementalCopy; } + + /** + * Sets the tier of the page blob. This is only supported for premium accounts. + * @param premiumPageBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + */ + protected void setPremiumPageBlobTier(PremiumPageBlobTier premiumPageBlobTier) { + this.premiumPageBlobTier = premiumPageBlobTier; + } + + /** + * Sets whether the blob tier is inferred. + * @param isBlobTierInferredTier + * A {@Link java.lang.Boolean} which specifies if the blob tier is inferred. + */ + protected void setBlobTierInferredTier(Boolean isBlobTierInferredTier) { + this.isBlobTierInferredTier = isBlobTierInferredTier; + } } \ No newline at end of file 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 904bfbd..23c4d90 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 @@ -54,6 +54,8 @@ final class BlobRequest { private static final String SNAPSHOTS_QUERY_ELEMENT_NAME = "snapshots"; + private static final String TIER_QUERY_ELEMENT_NAME = "tier"; + private static final String UNCOMMITTED_BLOBS_QUERY_ELEMENT_NAME = "uncommittedblobs"; /** @@ -219,6 +221,8 @@ public static HttpURLConnection appendBlock(final URI uri, final BlobRequestOpti * The snapshot version, if the source blob is a snapshot. * @param incrementalCopy * A boolean indicating whether or not this is an incremental copy. + * @param premiumPageBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. * @return a HttpURLConnection configured for the operation. * @throws StorageException * an exception representing any error which occurred during the operation. @@ -229,7 +233,7 @@ public static HttpURLConnection appendBlock(final URI uri, final BlobRequestOpti public static HttpURLConnection copyFrom(final URI uri, final BlobRequestOptions blobOptions, final OperationContext opContext, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, String source, final String sourceSnapshotID, - final boolean incrementalCopy) + final boolean incrementalCopy, final PremiumPageBlobTier premiumPageBlobTier) throws StorageException, IOException, URISyntaxException { if (sourceSnapshotID != null) { @@ -252,6 +256,10 @@ public static HttpURLConnection copyFrom(final URI uri, final BlobRequestOptions request.setRequestProperty(Constants.HeaderConstants.COPY_SOURCE_HEADER, source); + if (premiumPageBlobTier != null) { + request.setRequestProperty(BlobConstants.ACCESS_TIER_HEADER, String.valueOf(premiumPageBlobTier)); + } + if (sourceAccessCondition != null) { sourceAccessCondition.applySourceConditionToRequest(request); } @@ -1146,6 +1154,44 @@ public static HttpURLConnection listContainers(final URI uri, final BlobRequestO public static HttpURLConnection putBlob(final URI uri, final BlobRequestOptions blobOptions, final OperationContext opContext, final AccessCondition accessCondition, final BlobProperties properties, final BlobType blobType, final long pageBlobSize) throws IOException, URISyntaxException, StorageException { + return BlobRequest.putBlob(uri, blobOptions, opContext, accessCondition, properties, blobType, pageBlobSize, null /* premiumPageBlobTier */); + } + + /** + * Constructs a HttpURLConnection to upload a blob. Sign with blob length, or -1 for pageblob create. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param blobOptions + * A {@link BlobRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudBlobClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param properties + * The properties to set for the blob. + * @param blobType + * The type of the blob. + * @param pageBlobSize + * For a page blob, the size of the blob. This parameter is ignored for block blobs. + * @param premiumPageBlobTier + * A {@link PremiumPageBlobTier} object representing the tier to set. + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + */ + public static HttpURLConnection putBlob(final URI uri, final BlobRequestOptions blobOptions, + final OperationContext opContext, final AccessCondition accessCondition, final BlobProperties properties, + final BlobType blobType, final long pageBlobSize, final PremiumPageBlobTier premiumPageBlobTier) throws IOException, URISyntaxException, StorageException { if (blobType == BlobType.UNSPECIFIED) { throw new IllegalArgumentException(SR.BLOB_TYPE_NOT_DEFINED); } @@ -1165,6 +1211,11 @@ public static HttpURLConnection putBlob(final URI uri, final BlobRequestOptions request.setRequestProperty(BlobConstants.BLOB_TYPE_HEADER, BlobConstants.PAGE_BLOB); request.setRequestProperty(BlobConstants.SIZE, String.valueOf(pageBlobSize)); + if (premiumPageBlobTier != null) + { + request.setRequestProperty(BlobConstants.ACCESS_TIER_HEADER, String.valueOf(premiumPageBlobTier)); + } + properties.setLength(pageBlobSize); } else if (blobType == BlobType.BLOCK_BLOB){ @@ -1227,6 +1278,48 @@ public static HttpURLConnection putBlock(final URI uri, final BlobRequestOptions return request; } + + /** + * Constructs a HttpURLConnection to set the the tier on a page blob. + * This API is only supported for premium accounts. + * + * @param uri + * A java.net.URI object that specifies the absolute URI. + * @param blobOptions + * A {@link BlobRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify null to use the request options specified on the + * {@link CloudBlobClient}. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object representing the tier to set. + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + */ + public static HttpURLConnection setBlobTier(final URI uri, final BlobRequestOptions blobOptions, + final OperationContext opContext, final String premiumBlobTier) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.COMPONENT, TIER_QUERY_ELEMENT_NAME); + + final HttpURLConnection request = createURLConnection(uri, builder, blobOptions, opContext); + + request.setDoOutput(true); + request.setRequestMethod(Constants.HTTP_PUT); + request.setFixedLengthStreamingMode(0); + request.setRequestProperty(Constants.HeaderConstants.CONTENT_LENGTH, "0"); + request.setRequestProperty(BlobConstants.ACCESS_TIER_HEADER, premiumBlobTier); + + return request; + } /** * Constructs a HttpURLConnection to write a blob by specifying the list of block IDs that make up the blob. Sign 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 c31f05a..0e26efa 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 @@ -124,6 +124,26 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { properties.setAppendBlobCommittedBlockCount(Integer.parseInt(comittedBlockCount)); } + // Get the tier of the blob + final String premiumBlobTierString = request.getHeaderField(BlobConstants.ACCESS_TIER_HEADER); + + if (properties.getBlobType().equals(BlobType.PAGE_BLOB)) + { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(premiumBlobTierString); + properties.setPremiumPageBlobTier(premiumPageBlobTier); + } + else if (properties.getBlobType().equals(BlobType.UNSPECIFIED)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(premiumBlobTierString); + if (!premiumPageBlobTier.equals(PremiumPageBlobTier.UNKNOWN)) { + properties.setPremiumPageBlobTier(premiumPageBlobTier); + } + } + + final String tierInferredString = request.getHeaderField(BlobConstants.ACCESS_TIER_INFERRED_HEADER); + if (!Utility.isNullOrEmpty(tierInferredString)) { + properties.setBlobTierInferredTier(Boolean.parseBoolean(tierInferredString)); + } + final String incrementalCopyHeaderString = request.getHeaderField(Constants.HeaderConstants.INCREMENTAL_COPY); if (!Utility.isNullOrEmpty(incrementalCopyHeaderString)) { 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 5717b7a..a01ea97 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 @@ -681,6 +681,43 @@ public final String startCopy(final URI source) throws StorageException { */ @DoesServiceRequest public final String startCopy(final URI source, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { + return this.startCopy(source, null /* premiumPageBlobTier */, sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the + * specified premium page blob tier, access conditions, lease ID, request options, and operation context. + *

+ * Note: Setting the premiumPageBlobTier is only supported for premium accounts. + *

+ * @param source + * A java.net.URI The source URI. URIs for resources outside of Azure + * may only be copied into block blobs. + * @param premiumPageBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. + * Specifying null will use the default request options from the associated + * service client ({@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * This object is used to track requests to the storage service, and to provide additional + * runtime information about the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + protected final String startCopy(final URI source, final PremiumPageBlobTier premiumPageBlobTier, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -691,12 +728,12 @@ public final String startCopy(final URI source, final AccessCondition sourceAcce options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - this.startCopyImpl(source, false /* incrementalCopy */, sourceAccessCondition, destinationAccessCondition, options), + this.startCopyImpl(source, false /* incrementalCopy */, premiumPageBlobTier, sourceAccessCondition, destinationAccessCondition, options), options.getRetryPolicyFactory(), opContext); } protected StorageRequest startCopyImpl( - final URI source, final boolean incrementalCopy, final AccessCondition sourceAccessCondition, + final URI source, final boolean incrementalCopy, final PremiumPageBlobTier premiumPageBlobTier, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, final BlobRequestOptions options) { final StorageRequest putRequest = @@ -708,7 +745,7 @@ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, Op // 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.toASCIIString(), - blob.snapshotID, incrementalCopy); + blob.snapshotID, incrementalCopy, premiumPageBlobTier); } @Override @@ -732,6 +769,10 @@ public String preProcessResponse(CloudBlob blob, CloudBlobClient client, Operati blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); blob.properties.setCopyState(BlobResponse.getCopyState(this.getConnection())); + blob.properties.setPremiumPageBlobTier(premiumPageBlobTier); + if (premiumPageBlobTier != null) { + blob.properties.setBlobTierInferredTier(false); + } return blob.properties.getCopyState().getCopyId(); } @@ -2827,7 +2868,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation * A {@link StorageUri} object which represents the resource URI. * @param delimiter * A String which specifies the directory delimiter to use. - * @param usePathStyleUris + * @param container * A {@link CloudBlobContainer} object which represents the blob container. * * @return A String which represents the parent address for a blob URI. 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 4243a64..b8f8dc6 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 @@ -14,9 +14,7 @@ */ package com.microsoft.azure.storage.blob; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -212,6 +210,41 @@ public final String startCopy(final CloudPageBlob sourceBlob) throws StorageExce */ @DoesServiceRequest public final String startCopy(final CloudPageBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + return this.startCopy(sourceBlob, null /* premiumBlobTier */, sourceAccessCondition, destinationAccessCondition, options, opContext); + } + + /** + * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the + * specified blob tier, access conditions, lease ID, request options, and operation context. + * + * @param sourceBlob + * A CloudPageBlob object that represents the source blob to copy. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A String which represents the copy ID associated with the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * + */ + @DoesServiceRequest + public final String startCopy(final CloudPageBlob sourceBlob, final PremiumPageBlobTier premiumBlobTier, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException { Utility.assertNotNull("sourceBlob", sourceBlob); @@ -222,7 +255,7 @@ public final String startCopy(final CloudPageBlob sourceBlob, final AccessCondit source = sourceBlob.getServiceClient().getCredentials().transformUri(sourceBlob.getSnapshotQualifiedUri()); } - return this.startCopy(source, sourceAccessCondition, destinationAccessCondition, options, opContext); + return this.startCopy(source, premiumBlobTier, sourceAccessCondition, destinationAccessCondition, options, opContext); } /** @@ -341,7 +374,7 @@ public final String startIncrementalCopy(final URI sourceSnapshot, options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - this.startCopyImpl(sourceSnapshot, true /* incrementalCopy */, null /* sourceAccesCondition */, + this.startCopyImpl(sourceSnapshot, true /* incrementalCopy */, null /* premiumPageBlobTier */, null /* sourceAccesCondition */, destinationAccessCondition, options), options.getRetryPolicyFactory(), opContext); } @@ -429,6 +462,36 @@ public void create(final long length) throws StorageException { this.create(length, null /* accessCondition */, null /* options */, null /* opContext */); } + /** + * Creates a page blob using the specified request options and operation context. If the blob already exists, + * this will replace it. To instead throw an error if the blob already exists, use + * {@link AccessCondition#generateIfNotExistsCondition()}. + * + * @param length + * A long which represents the size, in bytes, of the page blob. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws IllegalArgumentException + * If the length is not a multiple of 512. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void create(final long length, final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + this.create(length, null /* premiumBlobTier */, accessCondition, options, opContext); + } + /** * Creates a page blob using the specified request options and operation context. If the blob already exists, * this will replace it. To instead throw an error if the blob already exists, use @@ -436,6 +499,8 @@ public void create(final long length) throws StorageException { * * @param length * A long which represents the size, in bytes, of the page blob. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the blob. * @param options @@ -454,7 +519,7 @@ public void create(final long length) throws StorageException { * If a storage service error occurred. */ @DoesServiceRequest - public void create(final long length, final AccessCondition accessCondition, BlobRequestOptions options, + public void create(final long length, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { assertNoWriteOperationForSnapshot(); @@ -469,10 +534,10 @@ public void create(final long length, final AccessCondition accessCondition, Blo options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - this.createImpl(length, accessCondition, options), options.getRetryPolicyFactory(), opContext); + this.createImpl(length, premiumBlobTier, accessCondition, options), options.getRetryPolicyFactory(), opContext); } - private StorageRequest createImpl(final long length, + private StorageRequest createImpl(final long length, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, final BlobRequestOptions options) { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @@ -481,7 +546,7 @@ private StorageRequest createImpl(final long l public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest.putBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), - options, context, accessCondition, blob.properties, BlobType.PAGE_BLOB, length); + options, context, accessCondition, blob.properties, BlobType.PAGE_BLOB, length, premiumBlobTier); } @Override @@ -506,6 +571,11 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); blob.getProperties().setLength(length); + blob.getProperties().setPremiumPageBlobTier(premiumBlobTier); + if (premiumBlobTier != null) { + blob.getProperties().setBlobTierInferredTier(false); + } + return null; } @@ -800,7 +870,7 @@ public List postProcessResponse(HttpURLConnection connection, Clo @DoesServiceRequest public BlobOutputStream openWriteExisting() throws StorageException { return this - .openOutputStreamInternal(null /* length */, null /* accessCondition */, null /* options */, null /* opContext */); + .openOutputStreamInternal(null /* length */, null /* premiumBlobTier */,null /* accessCondition */, null /* options */, null /* opContext */); } /** @@ -826,7 +896,7 @@ public BlobOutputStream openWriteExisting() throws StorageException { @DoesServiceRequest public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { - return this.openOutputStreamInternal(null /* length */, accessCondition, options, opContext); + return this.openOutputStreamInternal(null /* length */, null /* premiumBlobTier */, accessCondition, options, opContext); } /** @@ -849,7 +919,40 @@ public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobR @DoesServiceRequest public BlobOutputStream openWriteNew(final long length) throws StorageException { return this - .openOutputStreamInternal(length, null /* accessCondition */, null /* options */, null /* opContext */); + .openOutputStreamInternal(length, null /* premiumBlobTier */, null /* accessCondition */, null /* options */, null /* opContext */); + } + + /** + * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and + * operation context. The page blob does not need to yet exist and will be created with the length specified.If the + * blob already exists on the service, it will be overwritten. + *

+ * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using + * {@link AccessCondition#generateIfNotExistsCondition()}. + * + * @param length + * A long which represents the length, in bytes, of the stream to create. This value must be + * a multiple of 512. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link BlobOutputStream} object used to write data to the blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public BlobOutputStream openWriteNew(final long length, AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + return openOutputStreamInternal(length, null /* premiumBlobTier */, accessCondition, options, opContext); } /** @@ -863,6 +966,8 @@ public BlobOutputStream openWriteNew(final long length) throws StorageException * @param length * A long which represents the length, in bytes, of the stream to create. This value must be * a multiple of 512. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the blob. * @param options @@ -880,9 +985,9 @@ public BlobOutputStream openWriteNew(final long length) throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public BlobOutputStream openWriteNew(final long length, AccessCondition accessCondition, + public BlobOutputStream openWriteNew(final long length, final PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { - return openOutputStreamInternal(length, accessCondition, options, opContext); + return openOutputStreamInternal(length, premiumBlobTier, accessCondition, options, opContext); } /** @@ -894,6 +999,8 @@ public BlobOutputStream openWriteNew(final long length, AccessCondition accessCo * A long which represents the length, in bytes, of the stream to create. This value must be * a multiple of 512 or null if the * page blob already exists. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. * @param accessCondition * An {@link AccessCondition} object which represents the access conditions for the blob. * @param options @@ -910,7 +1017,7 @@ public BlobOutputStream openWriteNew(final long length, AccessCondition accessCo * @throws StorageException * If a storage service error occurred. */ - private BlobOutputStream openOutputStreamInternal(Long length, AccessCondition accessCondition, + private BlobOutputStream openOutputStreamInternal(Long length, PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); @@ -930,7 +1037,7 @@ private BlobOutputStream openOutputStreamInternal(Long length, AccessCondition a throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH); } - this.create(length, accessCondition, options, opContext); + this.create(length, premiumBlobTier, accessCondition, options, opContext); } else { this.downloadAttributes(accessCondition, options, opContext); @@ -1130,6 +1237,71 @@ public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, Opera return putRequest; } + /** + * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten. + * + * @param buffer + * A byte array which represents the data to write to the blob. + * @param offset + * A int which represents the offset of the byte array from which to start the data upload. + * @param length + * An int which represents the number of bytes to upload from the input buffer. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + */ + public void uploadFromByteArray(final byte[] buffer, final int offset, final int length, final PremiumPageBlobTier premiumBlobTier, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length); + this.upload(inputStream, length, premiumBlobTier, accessCondition, options, opContext); + inputStream.close(); + } + + /** + * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten. + * + * @param path + * A String which represents the path to the file to be uploaded. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * @throws IOException + */ + public void uploadFromFile(final String path, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException, IOException { + File file = new File(path); + long fileLength = file.length(); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + this.upload(inputStream, fileLength, premiumBlobTier, accessCondition, options, opContext); + inputStream.close(); + } + /** * Uploads the source stream data to the page blob. If the blob already exists on the service, it will be * overwritten. @@ -1148,13 +1320,13 @@ public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, Opera @Override @DoesServiceRequest public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException { - this.upload(sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */); + this.upload(sourceStream, length, null /* premiumBlobTier */, null /* accessCondition */, null /* options */, null /* opContext */); } /** * Uploads the source stream data to the page blob using the specified lease ID, request options, and operation * context. If the blob already exists on the service, it will be overwritten. - * + * * @param sourceStream * An {@link InputStream} object to read from. * @param length @@ -1170,7 +1342,7 @@ public void upload(final InputStream sourceStream, final long length) throws Sto * An {@link OperationContext} object which represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. - * + * * @throws IOException * If an I/O exception occurred. * @throws StorageException @@ -1179,6 +1351,39 @@ public void upload(final InputStream sourceStream, final long length) throws Sto @Override @DoesServiceRequest public void upload(final InputStream sourceStream, final long length, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException { + this.upload(sourceStream, length, null /* premiumBlobTier*/, accessCondition, options, opContext); + } + + /** + * Uploads the source stream data to the page blob using the specified lease ID, request options, and operation + * context. If the blob already exists on the service, it will be overwritten. + * + * @param sourceStream + * An {@link InputStream} object to read from. + * @param length + * A long which represents the length, in bytes, of the stream data. This must be great than + * zero and a multiple of 512. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param accessCondition + * An {@link AccessCondition} object which represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws IOException + * If an I/O exception occurred. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void upload(final InputStream sourceStream, final long length, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException { assertNoWriteOperationForSnapshot(); @@ -1201,7 +1406,7 @@ public void upload(final InputStream sourceStream, final long length, final Acce sourceStream.mark(Constants.MAX_MARK_LENGTH); } - final BlobOutputStream streamRef = this.openWriteNew(length, accessCondition, options, opContext); + final BlobOutputStream streamRef = this.openWriteNew(length, premiumBlobTier, accessCondition, options, opContext); try { streamRef.write(sourceStream, length); } @@ -1339,4 +1544,85 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) { this.streamWriteSizeInBytes = streamWriteSizeInBytes; } + + /** + * Sets the blob tier on a page blob on a premium storage account. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPremiumPageBlobTier(final PremiumPageBlobTier premiumBlobTier) throws StorageException { + this.uploadPremiumPageBlobTier(premiumBlobTier, null /* options */, null /* opContext */); + } + + /** + * Sets the tier on a page blob on a premium storage account. + * @param premiumBlobTier + * A {@link PremiumPageBlobTier} object which represents the tier of the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPremiumPageBlobTier(final PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + assertNoWriteOperationForSnapshot(); + Utility.assertNotNull("premiumBlobTier", premiumBlobTier); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); + + ExecutionEngine.executeWithRetry(this.blobServiceClient, this, + this.uploadPremiumPageBlobTierImpl(premiumBlobTier, options), options.getRetryPolicyFactory(), opContext); + } + + private StorageRequest uploadPremiumPageBlobTierImpl(final PremiumPageBlobTier premiumBlobTier, + final BlobRequestOptions options) { + final StorageRequest setTierRequest = new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + throws Exception { + return BlobRequest.setBlobTier(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, premiumBlobTier.toString()); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + blob.properties.setPremiumPageBlobTier(premiumBlobTier); + blob.properties.setBlobTierInferredTier(false); + + return null; + } + + }; + + return setTierRequest; + } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/PremiumPageBlobTier.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/PremiumPageBlobTier.java new file mode 100644 index 0000000..590a66c --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/PremiumPageBlobTier.java @@ -0,0 +1,112 @@ +/** + * Copyright Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.microsoft.azure.storage.blob; + +import java.util.Locale; + +import com.microsoft.azure.storage.core.Utility; + +/** + * The tier of the page blob. + * Please take a look at https://docs.microsoft.com/en-us/azure/storage/storage-premium-storage#scalability-and-performance-targets + * for detailed information on the corresponding IOPS and throughput per PremiumPageBlobTier. + */ +public enum PremiumPageBlobTier { + /** + * The tier is not recognized by this version of the library. + */ + UNKNOWN, + + /** + * P4 Tier + */ + P4, + + /** + * P6 Tier + */ + P6, + + /** + * P10 Tier + */ + P10, + + /** + * P20 Tier + */ + P20, + + /** + * P30 Tier + */ + P30, + + /** + * P40 Tier + */ + P40, + + /** + * P50 Tier + */ + P50, + + /** + * P60 Tier + */ + P60; + + /** + * Parses a premium page blob tier from the given string. + * + * @param premiumBlobTierString + * A String which contains the premium page blob tier to parse. + * + * @return A PremiumPageBlobTier value that represents the premium page blob tier. + */ + protected static PremiumPageBlobTier parse(final String premiumBlobTierString) { + if (Utility.isNullOrEmpty(premiumBlobTierString)) { + return UNKNOWN; + } + else if ("p4".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P4; + } + else if ("p6".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P6; + } + else if ("p10".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P10; + } + else if ("p20".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P20; + } + else if ("p30".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P30; + } + else if ("p40".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P40; + } + else if ("p50".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P50; + } + else if ("p60".equals(premiumBlobTierString.toLowerCase(Locale.US))) { + return P60; + } + else { + return UNKNOWN; + } + } +} \ No newline at end of file From 270c15c3fc51a56352cdfa7c1acb36b491e31185 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 11 Jul 2017 10:58:19 -0700 Subject: [PATCH 19/30] Removed request encryption check from Set Tier API --- .../src/com/microsoft/azure/storage/blob/CloudPageBlob.java | 1 - 1 file changed, 1 deletion(-) 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 b8f8dc6..14d9c71 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 @@ -1614,7 +1614,6 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); blob.properties.setPremiumPageBlobTier(premiumBlobTier); blob.properties.setBlobTierInferredTier(false); From af659082a36b535bd426f9e8100e73d6816bfba9 Mon Sep 17 00:00:00 2001 From: jofriedm-msft Date: Tue, 30 May 2017 13:06:26 -0700 Subject: [PATCH 20/30] Merge pull request #154 from rajeshbalamohan/liststatus_opt Minor CPU optimizations for listStatus --- .../azure/storage/core/Canonicalizer.java | 12 ++++++++- .../microsoft/azure/storage/core/Utility.java | 27 ++++++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java index 4b7f16e..0454d42 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/Canonicalizer.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.StorageException; @@ -45,6 +47,8 @@ abstract class Canonicalizer { */ private static final int ExpectedTableCanonicalizedStringLength = 200; + private static final Pattern CRLF = Pattern.compile("\r\n", Pattern.LITERAL); + /** * Add x-ms- prefixed headers in a fixed order. * @@ -85,7 +89,8 @@ private static void addCanonicalizedHeaders(final HttpURLConnection conn, final } // Unfolding is simply removal of CRLF. - final String unfoldedValue = value.replace("\r\n", Constants.EMPTY_STRING); + final String unfoldedValue = CRLF.matcher(value) + .replaceAll(Matcher.quoteReplacement(Constants.EMPTY_STRING)); // Append it to the canonicalized element string. canonicalizedElement.append(delimiter); @@ -251,6 +256,11 @@ protected static String getCanonicalizedResource(final java.net.URL address, fin final StringBuilder canonicalizedResource = new StringBuilder(resourcepath.toString()); // query parameters + if (address.getQuery() == null || !address.getQuery().contains("=")) { + //no query params. + return canonicalizedResource.toString(); + } + final Map queryVariables = PathUtility.parseQueryString(address.getQuery()); final Map lowercasedKeyNameValue = new HashMap(); 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 750cdfc..884fcd7 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 @@ -64,6 +64,20 @@ * RESERVED FOR INTERNAL USE. A class which provides utility methods. */ public final class Utility { + + /** + * Thread local for storing GMT date format. + */ + private static ThreadLocal + RFC1123_GMT_DATE_TIME_FORMATTER = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + final DateFormat formatter = new SimpleDateFormat(RFC1123_PATTERN, LOCALE_US); + formatter.setTimeZone(GMT_ZONE); + return formatter; + } + }; + /** * Stores a reference to the GMT time zone. */ @@ -473,21 +487,18 @@ public static String getGMTTime() { /** * Returns the GTM date/time String for the specified value using the RFC1123 pattern. - * + * * @param date * A Date object that represents the date to convert to GMT date/time in the RFC1123 * pattern. - * + * * @return A String that represents the GMT date/time for the specified value using the RFC1123 * pattern. */ public static String getGMTTime(final Date date) { - final DateFormat formatter = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US); - formatter.setTimeZone(GMT_ZONE); - return formatter.format(date); + return RFC1123_GMT_DATE_TIME_FORMATTER.get().format(date); } - /** * Returns the UTC date/time String for the specified value using Java's version of the ISO8601 pattern, * which is limited to millisecond precision. @@ -682,9 +693,7 @@ public static Date parseDateFromString(final String value, final String pattern, * If the specified string is invalid. */ public static Date parseRFC1123DateFromStringInGMT(final String value) throws ParseException { - final DateFormat format = new SimpleDateFormat(RFC1123_GMT_PATTERN, Utility.LOCALE_US); - format.setTimeZone(GMT_ZONE); - return format.parse(value); + return RFC1123_GMT_DATE_TIME_FORMATTER.get().parse(value); } /** From a69935ad1a43394deab70fb8129b7f74548fa3c7 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 2 Jun 2017 16:20:53 -0700 Subject: [PATCH 21/30] Encryption at REST for files --- ChangeLog.txt | 8 ++ .../file/CloudFileServerEncryptionTests.java | 127 ++++++++++++++++++ .../azure/storage/blob/CloudAppendBlob.java | 5 +- .../azure/storage/blob/CloudBlob.java | 7 +- .../azure/storage/blob/CloudBlockBlob.java | 6 +- .../azure/storage/blob/CloudPageBlob.java | 5 +- .../azure/storage/core/BaseResponse.java | 10 ++ .../azure/storage/file/CloudFile.java | 6 + .../storage/file/CloudFileDirectory.java | 3 + .../storage/file/FileDirectoryProperties.java | 24 ++++ .../azure/storage/file/FileProperties.java | 27 +++- .../azure/storage/file/FileResponse.java | 4 + 12 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileServerEncryptionTests.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 4b5b82d..275e1c6 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,7 +1,15 @@ +<<<<<<< HEAD 2017.XX.XX Version 1.1.0 * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. +======= +2017.XX.XX Version X.X.0 + * Added support for server side encryption for File Service. +2017.05.23 Version 5.2.0 + * Fixed Exists() calls on Shares and Directories to now populate metadata. This was already being done for Files. + * Changed blob constants to support up to 256 MB on put blob for block blobs. The default value for put blob threshold has also been updated to half of the maximum, or 128 MB currently. +>>>>>>> 6dea7a9... Encryption at REST for files * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. * Fixed a bug where an exception was being thrown on a range get of a blob or file when the options disableContentMD5Validation is set to false and useTransactionalContentMD5 is set to true and there is no overall MD5. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileServerEncryptionTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileServerEncryptionTests.java new file mode 100644 index 0000000..be81573 --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/file/CloudFileServerEncryptionTests.java @@ -0,0 +1,127 @@ +/** + * Copyright Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.microsoft.azure.storage.file; + + +import java.io.IOException; +import java.net.URISyntaxException; + +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.RequestCompletedEvent; +import com.microsoft.azure.storage.StorageEvent; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.TestRunners.CloudTests; +import com.microsoft.azure.storage.TestRunners.DevFabricTests; +import com.microsoft.azure.storage.TestRunners.DevStoreTests; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.junit.Assert.*; + +@Category({ CloudTests.class, DevFabricTests.class, DevStoreTests.class }) +//@Ignore +/* These test only works on accounts with server-side encryption enabled. */ +public class CloudFileServerEncryptionTests { + + private CloudFileShare share; + private CloudFileDirectory dir; + private CloudFile file; + private boolean requestFound; + + @Before + public void fileEncryptionTestMethodSetup() throws URISyntaxException, StorageException, IOException { + this.share = FileTestHelper.getRandomShareReference(); + this.share.create(); + this.dir = this.share.getRootDirectoryReference().getDirectoryReference("dir"); + this.dir.create(); + this.file = this.share.getRootDirectoryReference().getFileReference("file"); + this.file.uploadText("text"); + } + + @After + public void fileEncryptionTestMethodTearDown() throws StorageException { + this.share.deleteIfExists(); + } + + @Test + public void testFileAttributesEncryption() throws URISyntaxException, StorageException, IOException { + this.file.downloadAttributes(); + assertTrue(this.file.getProperties().isServerEncrypted()); + + CloudFile testFile = this.share.getRootDirectoryReference().getFileReference("file"); + testFile.downloadText(); + assertTrue(testFile.getProperties().isServerEncrypted()); + } + + @Test + public void testDirectoryAttributesEncryption() throws URISyntaxException, StorageException, IOException { + assertFalse(this.dir.getProperties().isServerEncrypted()); + + CloudFileDirectory testDir = this.share.getRootDirectoryReference().getDirectoryReference("dir"); + testDir.downloadAttributes(); + assertTrue(testDir.getProperties().isServerEncrypted()); + } + + @Test + public void testCloudFileUploadEncryption() throws URISyntaxException, StorageException, IOException { + this.requestFound = false; + + OperationContext ctxt = new OperationContext(); + ctxt.getRequestCompletedEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(RequestCompletedEvent eventArg) { + assertTrue(eventArg.getRequestResult().isRequestServiceEncrypted()); + CloudFileServerEncryptionTests.this.requestFound = true; + } + }); + + this.file.uploadText("test", null, null, null, ctxt); + assertTrue(this.requestFound); + + this.requestFound = false; + this.file.uploadProperties(null, null, ctxt); + assertTrue(this.requestFound); + + this.requestFound = false; + this.file.uploadMetadata(null, null, ctxt); + assertTrue(this.requestFound); + } + + @Test + public void testCloudFileDirectoryEncryption() throws URISyntaxException, StorageException, IOException { + this.requestFound = false; + + OperationContext ctxt = new OperationContext(); + ctxt.getRequestCompletedEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(RequestCompletedEvent eventArg) { + assertTrue(eventArg.getRequestResult().isRequestServiceEncrypted()); + CloudFileServerEncryptionTests.this.requestFound = true; + } + }); + + this.dir.uploadMetadata(null, null, ctxt); + assertTrue(this.requestFound); + + this.requestFound = false; + CloudFileDirectory dir2 = this.share.getRootDirectoryReference().getDirectoryReference("dir2"); + dir2.create(null, ctxt); + assertTrue(this.requestFound); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java index 5d82d32..6c44824 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudAppendBlob.java @@ -32,6 +32,7 @@ import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.StorageRequest; @@ -307,7 +308,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); blob.getProperties().setLength(0); return null; } @@ -455,7 +456,7 @@ public Long preProcessResponse(CloudAppendBlob blob, CloudBlobClient client, Ope blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); blob.updateCommittedBlockCountFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return appendOffset; } 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 a01ea97..b2d7432 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 @@ -44,6 +44,7 @@ import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageLocation; import com.microsoft.azure.storage.StorageUri; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.Logger; import com.microsoft.azure.storage.core.NetworkInputStream; @@ -2767,7 +2768,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; @@ -2917,8 +2918,4 @@ protected static String getParentNameFromURI(final StorageUri resourceAddress, f return parentName; } - - protected static boolean isServerRequestEncrypted(HttpURLConnection connection) { - return Constants.TRUE.equals(connection.getHeaderField(Constants.HeaderConstants.SERVER_REQUEST_ENCRYPTED)); - } } \ No newline at end of file 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 1445789..451031c 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 @@ -383,7 +383,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } @@ -763,7 +763,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } @@ -954,7 +954,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation return null; } - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } 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 14d9c71..fa0f102 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 @@ -31,6 +31,7 @@ import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; @@ -569,7 +570,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation } blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); blob.getProperties().setLength(length); blob.getProperties().setPremiumPageBlobTier(premiumBlobTier); if (premiumBlobTier != null) { @@ -1135,7 +1136,7 @@ public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, Opera blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); blob.updateSequenceNumberFromResponse(this.getConnection()); - this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection())); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseResponse.java index 555fa0a..68b75b0 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseResponse.java @@ -80,6 +80,16 @@ public static String getRequestId(final HttpURLConnection request) { return request.getHeaderField(Constants.HeaderConstants.REQUEST_ID_HEADER); } + /** + * Gets if the request was encrypted by the server. + * @param request + * The response from the server. + * @return A boolean indicating if the request was encrypted by the server. + */ + public static boolean isServerRequestEncrypted(HttpURLConnection request) { + return Constants.TRUE.equals(request.getHeaderField(Constants.HeaderConstants.SERVER_REQUEST_ENCRYPTED)); + } + /** * Returns all the header/value pairs with the given prefix. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index b185daa..b3ef2a1 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -50,6 +50,7 @@ import com.microsoft.azure.storage.blob.CloudBlob; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.Logger; import com.microsoft.azure.storage.core.NetworkInputStream; @@ -659,6 +660,7 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation } file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } @@ -2270,6 +2272,7 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation } file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; @@ -2360,6 +2363,7 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation } file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; @@ -2444,6 +2448,7 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation } file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; @@ -2524,6 +2529,7 @@ public Void preProcessResponse(CloudFile file, CloudFileClient client, Operation file.getProperties().setLength(size); file.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java index a479446..b3964df 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFileDirectory.java @@ -34,6 +34,7 @@ import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.LazySegmentedIterable; import com.microsoft.azure.storage.core.ListResponse; @@ -236,6 +237,7 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli final FileDirectoryAttributes attributes = FileResponse .getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris()); directory.setProperties(attributes.getProperties()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; @@ -615,6 +617,7 @@ public Void preProcessResponse(CloudFileDirectory directory, CloudFileClient cli } directory.updatePropertiesFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); return null; } }; diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java index c6dcfd3..525f2d2 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileDirectoryProperties.java @@ -33,6 +33,11 @@ public final class FileDirectoryProperties { */ private Date lastModified; + /** + * Represents the directory's server-side encryption status. + */ + private boolean serverEncrypted; + /** * Gets the ETag value of the directory. *

@@ -58,6 +63,15 @@ public Date getLastModified() { return this.lastModified; } + /** + * Gets the directory's server-side encryption status. + * + * @return A boolean which specifies the directory's encryption status. + */ + public boolean isServerEncrypted() { + return serverEncrypted; + } + /** * Sets the ETag value on the directory. * @@ -68,6 +82,16 @@ protected void setEtag(final String etag) { this.etag = etag; } + /** + * Sets the directory's server-side encryption status. + * + * @param serverEncrypted + * A boolean which specifies the encryption status to set. + */ + protected void setServerEncrypted(boolean serverEncrypted) { + this.serverEncrypted = serverEncrypted; + } + /** * Sets the last modified time on the directory. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java index f0ded01..9194fba 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileProperties.java @@ -76,6 +76,11 @@ public final class FileProperties { */ private Date lastModified; + /** + * Represents the file's server-side encryption status. + */ + private boolean serverEncrypted; + /** * Creates an instance of the FileProperties class. */ @@ -100,6 +105,7 @@ public FileProperties(final FileProperties other) { this.etag = other.etag; this.length = other.length; this.lastModified = other.lastModified; + this.serverEncrypted = other.serverEncrypted; } /** @@ -203,6 +209,15 @@ public long getLength() { return this.length; } + /** + * Gets the file's server-side encryption status. + * + * @return A boolean which specifies the file's encryption status. + */ + public boolean isServerEncrypted() { + return serverEncrypted; + } + /** * Sets the cache control value for the file. * @@ -262,7 +277,17 @@ public void setContentMD5(final String contentMD5) { public void setContentType(final String contentType) { this.contentType = contentType; } - + + /** + * Sets the file's server-side encryption status. + * + * @param serverEncrypted + * A boolean which specifies the encryption status to set. + */ + protected void setServerEncrypted(boolean serverEncrypted) { + this.serverEncrypted = serverEncrypted; + } + /** * Sets the copy state value for the file. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java index 40a1c51..a7ce697 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/FileResponse.java @@ -125,6 +125,8 @@ public static FileDirectoryAttributes getFileDirectoryAttributes(final HttpURLCo directoryProperties.setEtag(BaseResponse.getEtag(request)); directoryProperties.setLastModified(new Date(request.getLastModified())); directoryAttributes.setMetadata(getMetadata(request)); + directoryProperties.setServerEncrypted( + Constants.TRUE.equals(request.getHeaderField(Constants.HeaderConstants.SERVER_ENCRYPTED))); return directoryAttributes; } @@ -161,6 +163,8 @@ public static FileAttributes getFileAttributes(final HttpURLConnection request, properties.setContentType(request.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE)); properties.setEtag(BaseResponse.getEtag(request)); properties.setCopyState(FileResponse.getCopyState(request)); + properties.setServerEncrypted( + Constants.TRUE.equals(request.getHeaderField(Constants.HeaderConstants.SERVER_ENCRYPTED))); final Calendar lastModifiedCalendar = Calendar.getInstance(Utility.LOCALE_US); lastModifiedCalendar.setTimeZone(Utility.UTC_ZONE); From 8801080e1dd2920a2fbc000cb284a527744764ff Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 14 Jul 2017 10:57:23 -0700 Subject: [PATCH 22/30] Updating changelog --- ChangeLog.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 275e1c6..71cddfb 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,15 +1,7 @@ -<<<<<<< HEAD 2017.XX.XX Version 1.1.0 * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. - * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. -======= -2017.XX.XX Version X.X.0 * Added support for server side encryption for File Service. -2017.05.23 Version 5.2.0 - * Fixed Exists() calls on Shares and Directories to now populate metadata. This was already being done for Files. - * Changed blob constants to support up to 256 MB on put blob for block blobs. The default value for put blob threshold has also been updated to half of the maximum, or 128 MB currently. ->>>>>>> 6dea7a9... Encryption at REST for files * Fixed a bug that prevented setting content MD5 to true when creating a new file. * Fixed a bug where access conditions, options, and operation context were not being passed when calling openWriteExisting() on a page blob or a file. * Fixed a bug where an exception was being thrown on a range get of a blob or file when the options disableContentMD5Validation is set to false and useTransactionalContentMD5 is set to true and there is no overall MD5. From 8f228b8d2a8d453a37213e315ede136da8f3aa35 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 27 Jun 2017 16:22:42 -0700 Subject: [PATCH 23/30] Add error receiving response event --- ChangeLog.txt | 1 + .../azure/storage/EventFiringTests.java | 103 +++++++++++++++++- .../storage/ErrorReceivingResponseEvent.java | 38 +++++++ .../azure/storage/OperationContext.java | 68 +++++++++++- .../azure/storage/core/ExecutionEngine.java | 100 +++++++++-------- .../azure/storage/core/LogConstants.java | 1 + 6 files changed, 260 insertions(+), 51 deletions(-) create mode 100644 microsoft-azure-storage/src/com/microsoft/azure/storage/ErrorReceivingResponseEvent.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 71cddfb..df846ce 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,6 @@ 2017.XX.XX Version 1.1.0 * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. + * Added ErrorReceivingResponseEvent which fires when a network error occurs before the responseReceivedEvent fires. If the responseReceivedEvent fires sucessfully, this new event will not fire. * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. * Added support for server side encryption for File Service. * Fixed a bug that prevented setting content MD5 to true when creating a new file. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/EventFiringTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/EventFiringTests.java index 1264ebc..21ba73d 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/EventFiringTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/EventFiringTests.java @@ -17,15 +17,17 @@ import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; -import com.microsoft.azure.storage.blob.BlobRequestOptions; -import com.microsoft.azure.storage.blob.CloudBlobClient; -import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.blob.*; import com.microsoft.azure.storage.core.SR; +import org.apache.http.protocol.HTTP; import org.junit.Test; import org.junit.experimental.categories.Category; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.net.HttpURLConnection; +import java.net.SocketException; import java.net.URISyntaxException; import java.util.ArrayList; @@ -111,6 +113,22 @@ public void eventOccurred(ResponseReceivedEvent eventArg) { } }); + eventContext.getErrorReceivingResponseEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ErrorReceivingResponseEvent eventArg) { + fail("This event should not trigger"); + } + }); + + OperationContext.getGlobalErrorReceivingResponseEventHandler().addListener(new StorageEvent() { + + @Override + public void eventOccurred(ErrorReceivingResponseEvent eventArg) { + fail("This event should not trigger"); + } + }); + assertEquals(0, callList.size()); assertEquals(0, globalCallList.size()); @@ -138,6 +156,85 @@ public void eventOccurred(ResponseReceivedEvent eventArg) { assertEquals(2, globalCallList.size()); } + @Test + public void testErrorReceivingResponseEvent() throws URISyntaxException, StorageException { + final ArrayList callList = new ArrayList(); + final ArrayList globalCallList = new ArrayList(); + + OperationContext eventContext = new OperationContext(); + BlobRequestOptions options = new BlobRequestOptions(); + options.setRetryPolicyFactory(new RetryNoRetry()); + + // setting the sending request event handler to trigger an exception. + // this is a retryable exception + eventContext.getSendingRequestEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(SendingRequestEvent eventArg) { + HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject(); + connection.setFixedLengthStreamingMode(0); + } + }); + + eventContext.getErrorReceivingResponseEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(ErrorReceivingResponseEvent eventArg) { + assertEquals(eventArg.getRequestResult(), eventArg.getOpContext().getLastResult()); + callList.add(true); + } + }); + + OperationContext.getGlobalErrorReceivingResponseEventHandler().addListener(new StorageEvent() { + @Override + public void eventOccurred(ErrorReceivingResponseEvent eventArg) { + assertEquals(eventArg.getRequestResult(), eventArg.getOpContext().getLastResult()); + globalCallList.add(true); + } + }); + + CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); + CloudBlobContainer container = blobClient.getContainerReference("container1"); + container.createIfNotExists(); + + try { + CloudBlockBlob blob1 = container.getBlockBlobReference("blob1"); + try { + String blockID = String.format("%08d", 1); + blob1.uploadBlock(blockID, BlobTestHelper.getRandomDataStream(10), 10, null, options, eventContext); + } catch (Exception e) { } + + // make sure both the local and globab context update + assertEquals(1, callList.size()); + assertEquals(1, globalCallList.size()); + + // make sure only global updates by replacing the local with a no-op event + eventContext + .setErrorReceivingResponseEventHandler(new StorageEventMultiCaster>()); + try { + String blockID2 = String.format("%08d", 2); + blob1.uploadBlock(blockID2, BlobTestHelper.getRandomDataStream(10), 10, null, options, eventContext); + } catch (Exception e) { } + + assertEquals(1, callList.size()); + assertEquals(2, globalCallList.size()); + + // make sure global does not update by replacing the global with a no-op + OperationContext + .setGlobalErrorReceivingResponseEventHandler(new StorageEventMultiCaster>()); + + // make sure neither update + try { + String blockID3 = String.format("%08d", 3); + blob1.uploadBlock(blockID3, BlobTestHelper.getRandomDataStream(10), 10, null, options, eventContext); + } catch (Exception e) { } + + assertEquals(1, callList.size()); + assertEquals(2, globalCallList.size()); + } + finally { + container.deleteIfExists(); + } + } + @Test public void testRequestCompletedEvents() throws URISyntaxException, StorageException { final ArrayList callList = new ArrayList(); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/ErrorReceivingResponseEvent.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/ErrorReceivingResponseEvent.java new file mode 100644 index 0000000..84faaca --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/ErrorReceivingResponseEvent.java @@ -0,0 +1,38 @@ +/** + * Copyright Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.microsoft.azure.storage; + +/** + * Represents an event that is fired when a network error occurs before the HTTP response status and headers are received. + */ +public final class ErrorReceivingResponseEvent extends BaseEvent { + + /** + * Creates an instance of the BaseEvent class that is fired when a network error occurs before the HTTP response status and headers are received. + * + * @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 connectionObject + * Represents a connection object. Currently only java.net.HttpURLConnection is supported as + * a connection object. + * @param requestResult + * A {@link RequestResult} object that represents the current request result. + */ + public ErrorReceivingResponseEvent(OperationContext opContext, Object connectionObject, RequestResult requestResult) { + super(opContext, connectionObject, requestResult); + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java index 5aeb6c5..b0f072a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/OperationContext.java @@ -76,7 +76,8 @@ public final class OperationContext { private HashMap userHeaders; /** - * Represents an event that is triggered before sending a request. + * Represents an event that is triggered before sending a + * request. * * @see StorageEvent * @see StorageEventMultiCaster @@ -85,15 +86,23 @@ public final class OperationContext { private static StorageEventMultiCaster> globalSendingRequestEventHandler = new StorageEventMultiCaster>(); /** - * Represents an event that is triggered when a response is received from the storage service while processing a - * request. - * + * Represents an event that is triggered when a response is received from the storage service while processing a request + * * @see StorageEvent * @see StorageEventMultiCaster * @see ResponseReceivedEvent */ private static StorageEventMultiCaster> globalResponseReceivedEventHandler = new StorageEventMultiCaster>(); + /** + * Represents an event that is triggered when a network error occurs before the HTTP response status and headers are received. + * + * @see StorageEvent + * @see StorageEventMultiCaster + * @see ErrorReceivingResponseEvent + */ + private static StorageEventMultiCaster> globalErrorReceivingResponseEventHandler = new StorageEventMultiCaster>(); + /** * Represents an event that is triggered when a response received from the service is fully processed. * @@ -131,6 +140,15 @@ public final class OperationContext { */ private StorageEventMultiCaster> responseReceivedEventHandler = new StorageEventMultiCaster>(); + /** + * Represents an event that is triggered when a network error occurs before the HTTP response status and headers are received. + * + * @see StorageEvent + * @see StorageEventMultiCaster + * @see ErrorReceivingResponseEvent + */ + private StorageEventMultiCaster> errorReceivingResponseEventHandler = new StorageEventMultiCaster>(); + /** * Represents an event that is triggered when a response received from the service is fully processed. * @@ -263,6 +281,16 @@ public static StorageEventMultiCasterglobabErrorReceivingResponseEventHandler. + */ + public static StorageEventMultiCaster> getGlobalErrorReceivingResponseEventHandler() { + return OperationContext.globalErrorReceivingResponseEventHandler; + } + /** * Gets a global event multi-caster that is triggered when a request is completed. It allows event listeners to be * dynamically added and removed. @@ -313,6 +341,16 @@ public StorageEventMultiCastererrorReceivingResponseEventHandler. + */ + public StorageEventMultiCaster> getErrorReceivingResponseEventHandler() { + return this.errorReceivingResponseEventHandler; + } + /** * Gets an event multi-caster that is triggered when a request is completed. It allows event listeners to be * dynamically added and removed. @@ -421,6 +459,17 @@ public static void setGlobalResponseReceivedEventHandler( OperationContext.globalResponseReceivedEventHandler = globalResponseReceivedEventHandler; } + /** + * Sets a global event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received. + * + * @param globalErrorReceivingResponseEventHandler + * The {@link StorageEventMultiCaster} object to set for the globalErrorReceivingResponseEventHandler. + */ + public static void setGlobalErrorReceivingResponseEventHandler( + final StorageEventMultiCaster> globalErrorReceivingResponseEventHandler) { + OperationContext.globalErrorReceivingResponseEventHandler = globalErrorReceivingResponseEventHandler; + } + /** * Sets a global event multi-caster that is triggered when a request is completed. * @@ -477,6 +526,17 @@ public void setResponseReceivedEventHandler( this.responseReceivedEventHandler = responseReceivedEventHandler; } + /** + * Sets an event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received. + * + * @param errorReceivingResponseEventHandler + * The {@link StorageEventMultiCaster} object to set for the errorReceivingResponseEventHandler. + */ + public void setErrorReceivingResponseEventHandler( + final StorageEventMultiCaster> errorReceivingResponseEventHandler) { + this.errorReceivingResponseEventHandler = errorReceivingResponseEventHandler; + } + /** * Sets an event multi-caster that is triggered when a request is completed. * 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 2ebb906..95038f5 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 @@ -22,22 +22,7 @@ import java.util.Map.Entry; import java.util.concurrent.TimeoutException; import android.os.NetworkOnMainThreadException; -import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.LocationMode; -import com.microsoft.azure.storage.OperationContext; -import com.microsoft.azure.storage.RequestCompletedEvent; -import com.microsoft.azure.storage.RequestResult; -import com.microsoft.azure.storage.ResponseReceivedEvent; -import com.microsoft.azure.storage.RetryContext; -import com.microsoft.azure.storage.RetryInfo; -import com.microsoft.azure.storage.RetryNoRetry; -import com.microsoft.azure.storage.RetryPolicy; -import com.microsoft.azure.storage.RetryPolicyFactory; -import com.microsoft.azure.storage.RetryingEvent; -import com.microsoft.azure.storage.SendingRequestEvent; -import com.microsoft.azure.storage.StorageErrorCodeStrings; -import com.microsoft.azure.storage.StorageException; -import com.microsoft.azure.storage.StorageLocation; +import com.microsoft.azure.storage.*; /** * RESERVED FOR INTERNAL USE. A class that handles execution of StorageOperations and enforces retry policies. @@ -98,41 +83,55 @@ public static RESULT_TYPE executeWithRet request.getRequestProperty(Constants.HeaderConstants.DATE)); // 5. Potentially upload data - if (task.getSendStream() != null) { - Logger.info(opContext, LogConstants.UPLOAD); - final StreamMd5AndLength descriptor = Utility.writeToOutputStream(task.getSendStream(), - request.getOutputStream(), task.getLength(), false /* rewindStream */, - false /* calculate MD5 */, opContext, task.getRequestOptions()); - - task.validateStreamWrite(descriptor); - Logger.info(opContext, LogConstants.UPLOADDONE); - } + boolean responseReceivedEventTriggered = false; + try { + if (task.getSendStream() != null) { + Logger.info(opContext, LogConstants.UPLOAD); + final StreamMd5AndLength descriptor = Utility.writeToOutputStream(task.getSendStream(), + request.getOutputStream(), task.getLength(), false /* rewindStream */, + false /* calculate MD5 */, opContext, task.getRequestOptions()); + + task.validateStreamWrite(descriptor); + Logger.info(opContext, LogConstants.UPLOADDONE); + } + + Utility.logHttpRequest(request, opContext); - Utility.logHttpRequest(request, opContext); + // 6. Process the request - Get response + RequestResult currResult = task.getResult(); + currResult.setStartDate(new Date()); - // 6. Process the request - Get response - RequestResult currResult = task.getResult(); - currResult.setStartDate(new Date()); + Logger.info(opContext, LogConstants.GET_RESPONSE); - Logger.info(opContext, LogConstants.GET_RESPONSE); + currResult.setStatusCode(request.getResponseCode()); + currResult.setStatusMessage(request.getResponseMessage()); + currResult.setStopDate(new Date()); - currResult.setStatusCode(request.getResponseCode()); - currResult.setStatusMessage(request.getResponseMessage()); + currResult.setServiceRequestID(BaseResponse.getRequestId(request)); + currResult.setEtag(BaseResponse.getEtag(request)); + currResult.setRequestDate(BaseResponse.getDate(request)); + currResult.setContentMD5(BaseResponse.getContentMD5(request)); - currResult.setStopDate(new Date()); - currResult.setServiceRequestID(BaseResponse.getRequestId(request)); - currResult.setEtag(BaseResponse.getEtag(request)); - currResult.setRequestDate(BaseResponse.getDate(request)); - currResult.setContentMD5(BaseResponse.getContentMD5(request)); + // 7. Fire ResponseReceived Event + responseReceivedEventTriggered = true; + ExecutionEngine.fireResponseReceivedEvent(opContext, request, task.getResult()); - // 7. Fire ResponseReceived Event - ExecutionEngine.fireResponseReceivedEvent(opContext, request, task.getResult()); + Logger.info(opContext, LogConstants.RESPONSE_RECEIVED, currResult.getStatusCode(), + currResult.getServiceRequestID(), currResult.getContentMD5(), currResult.getEtag(), + currResult.getRequestDate()); - Logger.info(opContext, LogConstants.RESPONSE_RECEIVED, currResult.getStatusCode(), - currResult.getServiceRequestID(), currResult.getContentMD5(), currResult.getEtag(), - currResult.getRequestDate()); - - Utility.logHttpResponse(request, opContext); + Utility.logHttpResponse(request, opContext); + } + finally { + Logger.info(opContext, LogConstants.ERROR_RECEIVING_RESPONSE); + if (!responseReceivedEventTriggered) { + if (task.getResult().getStartDate() == null) { + task.getResult().setStartDate(new Date()); + } + + ExecutionEngine.fireErrorReceivingResponseEvent(opContext, request, task.getResult()); + } + } // 8. Pre-process response to check if there was an exception. Do Response parsing (headers etc). Logger.info(opContext, LogConstants.PRE_PROCESS); @@ -383,6 +382,19 @@ private static void fireResponseReceivedEvent(OperationContext opContext, HttpUR } } + /** + * Fires events representing that an error occurred when receiving the response. + */ + private static void fireErrorReceivingResponseEvent(OperationContext opContext, HttpURLConnection request, + RequestResult result) { + if (opContext.getErrorReceivingResponseEventHandler().hasListeners() + || OperationContext.getGlobalErrorReceivingResponseEventHandler().hasListeners()) { + ErrorReceivingResponseEvent event = new ErrorReceivingResponseEvent(opContext, request, result); + opContext.getErrorReceivingResponseEventHandler().fireEvent(event); + OperationContext.getGlobalErrorReceivingResponseEventHandler().fireEvent(event); + } + } + /** * Fires events representing that a response received from the service is fully processed. */ 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 55fd5be..a29cc3c 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 @@ -22,6 +22,7 @@ public class LogConstants { public static final String COMPLETE = "Operation completed."; public static final String DO_NOT_RETRY_POLICY = "Retry policy did not allow for a retry. Failing. Error Message = '%s'."; public static final String DO_NOT_RETRY_TIMEOUT = "Operation cannot be retried because maximum execution timeout has been reached. Failing. Inner error Message = '%s'."; + public static final String ERROR_RECEIVING_RESPONSE = "A network error occurred before the HTTP response status and headers were received."; public static final String GET_RESPONSE = "Waiting for response."; public static final String INIT_LOCATION = "Starting operation with location '%s' per location mode '%s'."; public static final String NEXT_LOCATION = "The next location has been set to '%s', per location mode '%s'."; From 27b2cecd757cb5b9c9b31423edad882e52353dcf Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 14 Jul 2017 13:25:58 -0700 Subject: [PATCH 24/30] Cleaning up commits --- ChangeLog.txt | 2 +- .../microsoft/azure/storage/TestHelper.java | 63 +++++++++++-------- .../storage/blob/CloudPageBlobTests.java | 4 +- .../azure/storage/RequestResult.java | 7 +++ .../azure/storage/blob/CloudBlockBlob.java | 1 + .../microsoft/azure/storage/core/Utility.java | 8 +-- 6 files changed, 50 insertions(+), 35 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index df846ce..660ff6c 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,6 @@ 2017.XX.XX Version 1.1.0 + * Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. - * Added ErrorReceivingResponseEvent which fires when a network error occurs before the responseReceivedEvent fires. If the responseReceivedEvent fires sucessfully, this new event will not fire. * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. * Added support for server side encryption for File Service. * Fixed a bug that prevented setting content MD5 to true when creating a new file. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java index a2dfca9..596c206 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java @@ -14,12 +14,6 @@ */ package com.microsoft.azure.storage; -<<<<<<< HEAD -======= -import static org.junit.Assert.*; -import static org.junit.Assume.assumeNotNull; - ->>>>>>> 44be010... Premium Page Blob Tiers import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; @@ -29,28 +23,8 @@ import java.util.Arrays; import java.util.Random; -<<<<<<< HEAD import junit.framework.Assert; -import com.microsoft.azure.storage.SharedAccessAccountPolicy; -======= -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.junit.AssumptionViolatedException; -import org.w3c.dom.DOMException; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import com.microsoft.azure.keyvault.extensions.RsaKey; -import com.microsoft.azure.keyvault.extensions.SymmetricKey; ->>>>>>> 44be010... Premium Page Blob Tiers import com.microsoft.azure.storage.analytics.CloudAnalyticsClient; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.file.CloudFileClient; @@ -59,11 +33,16 @@ public class TestHelper { private static CloudStorageAccount account; + private static CloudStorageAccount premiumAccount; public static String connectionString; + public static String premiumConnectionString; public static String accountName; + public static String premiumAccountName; public static String accountKey; + public static String premiumAccountKey; public static StorageUri blobEndpoint; + public static StorageUri premiumBlobEndpoint; public static StorageUri queueEndpoint; public static StorageUri tableEndpoint; @@ -214,6 +193,38 @@ else if (accountName != null && accountKey != null) { return account; } + private static CloudStorageAccount getPremiumBlobAccount() throws StorageException { + // Only do this the first time TestBase is called as storage account is static + if (premiumAccount == null) { + // if connectionString is set, use that as an account string + // if accountName and accountKey are set, use those to setup the account with default endpoints + // if all of the endpoints are set, use those to create custom endpoints + try { + if (premiumConnectionString != null) { + premiumAccount = CloudStorageAccount.parse(connectionString); + } + else if (premiumAccountName != null && premiumAccountKey != null) { + StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(premiumAccountName, premiumAccountKey); + if(premiumBlobEndpoint == null){ + premiumAccount = new CloudStorageAccount(credentials); + } else { + premiumAccount = new CloudStorageAccount(credentials,blobEndpoint, null , null); + } + } else { + throw new StorageException("CredentialsNotSpecified", + "Credentials must be specified in the TestHelper class in order to run tests.", + Constants.HeaderConstants.HTTP_UNUSED_306, + null, + null); + } + } + catch (Exception e) { + throw StorageException.translateException(null, e, null); + } + } + return premiumAccount; + } + protected static void enableFiddler() { System.setProperty("http.proxyHost", "localhost"); System.setProperty("http.proxyPort", "8888"); 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 a010453..57d4511 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 @@ -414,7 +414,7 @@ public void testCloudPageBlobUploadFromStreamWithAccessCondition() throws URISyn blob1.upload(srcStream, length, accessCondition, null, null); } catch (StorageException ex) { - Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); } srcStream.reset(); @@ -429,7 +429,7 @@ public void testCloudPageBlobUploadFromStreamWithAccessCondition() throws URISyn blob1.upload(srcStream, length, accessCondition, null, null); } catch (StorageException ex) { - Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); + assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode()); } srcStream.reset(); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java index 7997f18..7743019 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java @@ -173,6 +173,13 @@ public String getStatusMessage() { * @return A java.util.Date object which contains the stop date. */ public Date getStopDate() { + if (this.stopDate == null) + { + // stop date was not initialized, most likely due to a network exception + // if this is null, retries are immediate + this.stopDate = new Date(); + } + return this.stopDate; } 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 451031c..396765a 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 @@ -32,6 +32,7 @@ import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; import com.microsoft.azure.storage.core.Base64; +import com.microsoft.azure.storage.core.BaseResponse; import com.microsoft.azure.storage.core.ExecutionEngine; import com.microsoft.azure.storage.core.RequestLocationMode; import com.microsoft.azure.storage.core.SR; 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 884fcd7..3f61272 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 @@ -96,7 +96,7 @@ protected DateFormat initialValue() { /** * Stores a reference to the RFC1123 date/time pattern. */ - private static final String RFC1123_GMT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; + private static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z"; /** * Stores a reference to the ISO8601 date/time pattern. @@ -1174,12 +1174,8 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr */ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, -<<<<<<< HEAD - final RequestOptions options, StorageRequest request) throws IOException, StorageException { -======= final RequestOptions options, final Boolean shouldFlush, StorageRequest request, StreamMd5AndLength descriptor) throws IOException, StorageException { ->>>>>>> 1c67198... Fix transactional MD5 validations on recovery if (rewindSourceStream && sourceStream.markSupported()) { sourceStream.reset(); sourceStream.mark(Constants.MAX_MARK_LENGTH); @@ -1238,7 +1234,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr count = sourceStream.read(retrievedBuff, 0, nextCopy); } - if (outStream != null) { + if (outStream != null && shouldFlush) { outStream.flush(); } From 6cb0b372db2348bc30108a2e9b4b2f4b90c13517 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 17 Jul 2017 10:35:56 -0700 Subject: [PATCH 25/30] Undoing change to date time formatter --- .../microsoft/azure/storage/core/Utility.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) 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 3f61272..fee0410 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 @@ -65,19 +65,6 @@ */ public final class Utility { - /** - * Thread local for storing GMT date format. - */ - private static ThreadLocal - RFC1123_GMT_DATE_TIME_FORMATTER = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - final DateFormat formatter = new SimpleDateFormat(RFC1123_PATTERN, LOCALE_US); - formatter.setTimeZone(GMT_ZONE); - return formatter; - } - }; - /** * Stores a reference to the GMT time zone. */ @@ -96,7 +83,7 @@ protected DateFormat initialValue() { /** * Stores a reference to the RFC1123 date/time pattern. */ - private static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z"; + private static final String RFC1123_GMT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; /** * Stores a reference to the ISO8601 date/time pattern. @@ -496,7 +483,9 @@ public static String getGMTTime() { * pattern. */ public static String getGMTTime(final Date date) { - return RFC1123_GMT_DATE_TIME_FORMATTER.get().format(date); + final DateFormat formatter = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US); + formatter.setTimeZone(GMT_ZONE); + return formatter.format(date); } /** @@ -693,7 +682,9 @@ public static Date parseDateFromString(final String value, final String pattern, * If the specified string is invalid. */ public static Date parseRFC1123DateFromStringInGMT(final String value) throws ParseException { - return RFC1123_GMT_DATE_TIME_FORMATTER.get().parse(value); + final DateFormat format = new SimpleDateFormat(RFC1123_GMT_PATTERN, Utility.LOCALE_US); + format.setTimeZone(GMT_ZONE); + return format.parse(value); } /** From 1bffed94453678d0fdb639cc319a57f0863122a6 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 17 Jul 2017 10:52:11 -0700 Subject: [PATCH 26/30] Fix test helper for premium tests --- .../src/com/microsoft/azure/storage/TestHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java index 596c206..9bc11e0 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java @@ -47,8 +47,8 @@ public class TestHelper { public static StorageUri tableEndpoint; public static CloudBlobClient createCloudBlobClient() throws StorageException { - return getAccount().createCloudBlobClient(); - } + return getAccount().createCloudBlobClient(); + } public static CloudFileClient createCloudFileClient() throws StorageException { return getAccount().createCloudFileClient(); @@ -201,14 +201,14 @@ private static CloudStorageAccount getPremiumBlobAccount() throws StorageExcepti // if all of the endpoints are set, use those to create custom endpoints try { if (premiumConnectionString != null) { - premiumAccount = CloudStorageAccount.parse(connectionString); + premiumAccount = CloudStorageAccount.parse(premiumConnectionString); } else if (premiumAccountName != null && premiumAccountKey != null) { StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(premiumAccountName, premiumAccountKey); if(premiumBlobEndpoint == null){ premiumAccount = new CloudStorageAccount(credentials); } else { - premiumAccount = new CloudStorageAccount(credentials,blobEndpoint, null , null); + premiumAccount = new CloudStorageAccount(credentials, premiumBlobEndpoint, null , null); } } else { throw new StorageException("CredentialsNotSpecified", From 65150a9f06161a1d6eb0fe28adb7837fa30369ad Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 26 Jul 2017 13:18:49 -0700 Subject: [PATCH 27/30] Removing extra param to WriteToOutputStream --- .../src/com/microsoft/azure/storage/core/Utility.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 fee0410..23c49ac 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 @@ -1124,7 +1124,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, final RequestOptions options, final Boolean shouldFlush) throws IOException, StorageException { return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext, - options, shouldFlush, null /*StorageRequest*/, null /* descriptor */); + options, null /*StorageRequest*/, null /* descriptor */); } /** @@ -1165,7 +1165,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr */ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream, long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext, - final RequestOptions options, final Boolean shouldFlush, StorageRequest request, StreamMd5AndLength descriptor) + final RequestOptions options, StorageRequest request, StreamMd5AndLength descriptor) throws IOException, StorageException { if (rewindSourceStream && sourceStream.markSupported()) { sourceStream.reset(); @@ -1225,7 +1225,7 @@ public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStr count = sourceStream.read(retrievedBuff, 0, nextCopy); } - if (outStream != null && shouldFlush) { + if (outStream != null) { outStream.flush(); } From 896832510a646367606e5ae2c9eceec121d5befb Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 26 Jul 2017 13:41:07 -0700 Subject: [PATCH 28/30] Updating version for 1.1.0 release --- ChangeLog.txt | 2 +- README.md | 4 ++-- microsoft-azure-storage-samples/AndroidManifest.xml | 2 +- microsoft-azure-storage-test/AndroidManifest.xml | 2 +- microsoft-azure-storage/AndroidManifest.xml | 2 +- microsoft-azure-storage/pom.xml | 2 +- .../src/com/microsoft/azure/storage/Constants.java | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 660ff6c..7519f92 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,4 @@ -2017.XX.XX Version 1.1.0 +2017.07.26 Version 1.1.0 * Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. diff --git a/README.md b/README.md index acb8e95..28dc243 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,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:1.0.0@aar' + compile 'com.microsoft.azure.android:azure-storage-android:1.1.0@aar' } ### Option 4: aar via Maven @@ -57,7 +57,7 @@ To get the binaries of this library as distributed by Microsoft, ready for use w com.microsoft.azure.android azure-storage-android - 1.0.0 + 1.1.0 aar ``` diff --git a/microsoft-azure-storage-samples/AndroidManifest.xml b/microsoft-azure-storage-samples/AndroidManifest.xml index b2e19b5..a902139 100644 --- a/microsoft-azure-storage-samples/AndroidManifest.xml +++ b/microsoft-azure-storage-samples/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="1.1.0" > + android:versionName="1.1.0" > diff --git a/microsoft-azure-storage/pom.xml b/microsoft-azure-storage/pom.xml index 2d29471..5a5cc45 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 - 1.0.0 + 1.1.0 aar Microsoft Azure Storage Android Client SDK diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java index 2f552c6..ffbcfb4 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -595,7 +595,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "1.0.0"; + public static final String USER_AGENT_VERSION = "1.1.0"; /** * The default type for content-type and accept From 0e33cc07e11f1b7661dba2f4f7ab0ffaa6befb32 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 31 Jul 2017 10:07:00 -0700 Subject: [PATCH 29/30] Updating date in changelog --- ChangeLog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 7519f92..2f2e5ff 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,4 @@ -2017.07.26 Version 1.1.0 +2017.07.31 Version 1.1.0 * Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. * Fixed a bug where the transactional MD5 check would fail when downloading a range of blob or file and the recovery action is performed on a subsection of the range. From da600add62862d4992982a052cf4d6f1b6d209ab Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 31 Jul 2017 10:12:28 -0700 Subject: [PATCH 30/30] Removing extra parameters for calls to writeToOutputStream --- .../src/com/microsoft/azure/storage/blob/CloudBlob.java | 2 +- .../src/com/microsoft/azure/storage/file/CloudFile.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b2d7432..e8d0605 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 @@ -1387,7 +1387,7 @@ public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, // writeToOutputStream will update the currentRequestByteCount on this request in case a retry // is needed and download should resume from that point final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false, - validateMD5, context, options, true, this, this.getCurrentDescriptor()); + validateMD5, context, options, this, this.getCurrentDescriptor()); // length was already checked by the NetworkInputStream, now check Md5 if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java index b3ef2a1..48b5ec8 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/file/CloudFile.java @@ -1403,7 +1403,7 @@ public Integer postProcessResponse(HttpURLConnection connection, CloudFile file, // writeToOutputStream will update the currentRequestByteCount on this request in case a retry // is needed and download should resume from that point final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false, - validateMD5, context, options, true, this, this.getCurrentDescriptor()); + validateMD5, context, options, this, this.getCurrentDescriptor()); // length was already checked by the NetworkInputStream, now check Md5 if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) {