diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8796767
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,50 @@
+If you intend to contribute to the project, please make sure you've followed the instructions provided in the [Azure Projects Contribution Guidelines](http://azure.github.io/guidelines/).
+## Project Setup
+The Azure Storage development team uses Eclipse so instructions will be tailored to that preference. However, any preferred IDE or other toolset should be usable.
+
+### Install
+* Java SE 6+
+* [Eclipse](https://eclipse.org/downloads/)
+* [Maven plugin for Eclipse](http://www.eclipse.org/m2e/index.html). Some Eclipse packages (ex Eclipse IDE for Java Developers) may come with this plugin already installed.
+* [Maven](https://maven.apache.org/install.html)
+* Clone the source code from GitHub
+
+### Open Solution
+Open the project from Eclipse using File->Import->Maven->Existing Maven Projects and navigating to the azure-storage-java folder. Select the listed pom. This imports the source and the test files and downloads the required dependencies via Maven. If you'd like to import the samples, follow the same procedure but navigate to the azure-storage-java\microsoft-azure-storage-samples folder and select that pom. Both projects can be opened at the same time and will be shown in the Package Explorer.
+
+## Tests
+
+### Configuration
+The only step to configure testing is to setup a configuration file or connection string via environment variables. To use the connection string route, create an environment variable named "storageConnection". To use the configuration file route, create an environment variable named "storageTestConfiguration" with the path to a TestConfigurations.xml file with this [template](https://github.com/Azure/azure-storage-java/blob/master/microsoft-azure-storage-test/res/TestConfigurations.xml).
+
+### Running
+To actually run tests, right click on the test class in the Package Explorer or the individual test in the Outline and select Run As->JUnitTest. All tests or tests grouped by service can be run using the test runners in the com.microsoft.azure.storage package TestRunners file. Running all tests from the top of the package explorer will result in each test being run multiple times as the package explorer will also run every test runner.
+
+### Testing Features
+As you develop a feature, you'll need to write tests to ensure quality. You should also run existing tests related to your change to address any unexpected breaks.
+
+## Pull Requests
+
+### Guidelines
+The following are the minimum requirements for any pull request that must be met before contributions can be accepted.
+* Make sure you've signed the CLA before you start working on any change.
+* Discuss any proposed contribution with the team via a GitHub issue **before** starting development.
+* Code must be professional quality
+ * No style issues
+ * You should strive to mimic the style with which we have written the library
+ * Clean, well-commented, well-designed code
+ * Try to limit the number of commits for a feature to 1-2. If you end up having too many we may ask you to squash your changes into fewer commits.
+* [ChangeLog.md](ChangeLog.md) needs to be updated describing the new change
+* Thoroughly test your feature
+
+### Branching Policy
+Changes should be based on the **dev** branch, not master as master is considered publicly released code. If after discussion with us breaking changes are considered for the library, we will create a **dev_breaking** branch based on dev which can be used to store these changes until the next breaking release. Each breaking change should be recorded in [BreakingChanges.md](BreakingChanges.md).
+
+### Adding Features for Java 6+
+We strive to release each new feature in a backward compatible manner. Therefore, we ask that all contributions be written to work in Java 6, 7 and 8.
+
+### Review Process
+We expect all guidelines to be met before accepting a pull request. As such, we will work with you to address issues we find by leaving comments in your code. Please understand that it may take a few iterations before the code is accepted as we maintain high standards on code quality. Once we feel comfortable with a contribution, we will validate the change and accept the pull request.
+
+
+Thank you for any contributions! Please let the team know if you have any questions or concerns about our contribution policy.
\ No newline at end of file
diff --git a/ChangeLog.txt b/ChangeLog.txt
index 4606838..d1c10f0 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,3 +1,8 @@
+2016.07.06 Version 4.3.0
+ * Added support for server-side encryption.
+ * Added support for getBlobReferenceFromServer methods on CloudBlobContainer to support retrieving a blob without knowing its type.
+ * Fixed a bug in the retry policies where 300 status codes were being retried when they shouldn't be.
+
2016.04.07 Version 4.2.0
* Added support for setting a library-wide proxy. The default proxy can be set on OperationContext.
* Added support in Page Blob for getting a list of differing page ranges between snapshot versions.
diff --git a/microsoft-azure-storage-test/res/TestConfigurations.xml b/microsoft-azure-storage-test/res/TestConfigurations.xml
index 39db98e..bb3250d 100644
--- a/microsoft-azure-storage-test/res/TestConfigurations.xml
+++ b/microsoft-azure-storage-test/res/TestConfigurations.xml
@@ -21,6 +21,7 @@
http://[ACCOUNT].blob.core.windows.net
http://[ACCOUNT].queue.core.windows.net
http://[ACCOUNT].table.core.windows.net
+ http://[ACCOUNT].file.core.windows.net
http://[ACCOUNT]-secondary.blob.core.windows.net
http://[ACCOUNT]-secondary.queue.core.windows.net
http://[ACCOUNT]-secondary.table.core.windows.net
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/SecondaryTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/SecondaryTests.java
index c3b5252..28683e0 100644
--- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/SecondaryTests.java
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/SecondaryTests.java
@@ -16,6 +16,8 @@
import static org.junit.Assert.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.util.ArrayList;
@@ -30,6 +32,7 @@
import com.microsoft.azure.storage.TestRunners.SlowTests;
import com.microsoft.azure.storage.blob.BlobRequestOptions;
import com.microsoft.azure.storage.blob.BlobTestHelper;
+import com.microsoft.azure.storage.blob.BlobType;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
@@ -639,6 +642,32 @@ public void testMultiLocationRetriesTable() throws URISyntaxException, StorageEx
testTableDownloadPermissions(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
retryContextList, retryInfoList);
}
+
+ @Test
+ public void testRetryOn304() throws StorageException, IOException, URISyntaxException {
+ OperationContext operationContext = new OperationContext();
+ operationContext.getRetryingEventHandler().addListener(new StorageEvent() {
+ @Override
+ public void eventOccurred(RetryingEvent eventArg) {
+ fail("Request should not be retried.");
+ }
+ });
+
+ CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();
+ try {
+ container.create();
+ CloudBlockBlob blockBlobRef = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(container, BlobType.BLOCK_BLOB,
+ "originalBlob", 1024, null);
+ AccessCondition accessCondition = AccessCondition.generateIfNoneMatchCondition(blockBlobRef.getProperties().getEtag());
+ blockBlobRef.download(new ByteArrayOutputStream(), accessCondition, null, operationContext);
+
+ fail("Download should fail with a 304.");
+ } catch (StorageException ex) {
+ assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage());
+ } finally {
+ container.deleteIfExists();
+ }
+ }
private static void AddUpdatedLocationModes(List retryContextList, List retryInfoList) {
retryInfoList
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 e50b67c..f2ec1c7 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
@@ -13,7 +13,8 @@
import com.microsoft.azure.storage.blob.CloudBlobClientTests;
import com.microsoft.azure.storage.blob.CloudBlobContainerTests;
import com.microsoft.azure.storage.blob.CloudBlobDirectoryTests;
-import com.microsoft.azure.storage.blob.CloudBlobEncryptionTests;
+import com.microsoft.azure.storage.blob.CloudBlobClientEncryptionTests;
+import com.microsoft.azure.storage.blob.CloudBlobServerEncryptionTests;
import com.microsoft.azure.storage.blob.CloudBlockBlobTests;
import com.microsoft.azure.storage.blob.CloudPageBlobTests;
import com.microsoft.azure.storage.blob.LeaseTests;
@@ -100,8 +101,8 @@ public static class CoreTestSuite {
@RunWith(Suite.class)
@SuiteClasses({ BlobOutputStreamTests.class, CloudBlobClientTests.class, CloudBlobContainerTests.class,
- CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class,
- CloudBlobEncryptionTests.class, LeaseTests.class, SasTests.class })
+ CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class,
+ CloudBlobClientEncryptionTests.class, CloudBlobServerEncryptionTests.class, LeaseTests.class, SasTests.class })
public static class BlobTestSuite {
}
@@ -130,7 +131,8 @@ public static class AnalyticsTestSuite {
}
@RunWith(Suite.class)
- @SuiteClasses({ CloudBlobEncryptionTests.class, CloudQueueEncryptionTests.class, TableEncryptionTests.class })
+ @SuiteClasses({ CloudBlobClientEncryptionTests.class, CloudBlobServerEncryptionTests.class,
+ CloudQueueEncryptionTests.class, TableEncryptionTests.class })
public static class EncryptionTestSuite {
}
@@ -161,4 +163,4 @@ public static class DevFabricNoSecondarySuite {
@SuiteClasses(AllTestSuite.class)
public static class FastTestSuite {
}
-}
+}
\ 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 aeb5fd2..0a2334c 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
@@ -384,6 +384,7 @@ public static void assertAreEqual(CloudBlob blob1, CloudBlob blob2) throws URISy
}
else {
assertNotNull(blob2);
+ assertEquals(blob1.getClass(), blob2.getClass());
assertEquals(blob1.getUri(), blob2.getUri());
assertEquals(blob1.getSnapshotID(), blob2.getSnapshotID());
assertEquals(blob1.isSnapshot(), blob2.isSnapshot());
@@ -399,6 +400,7 @@ public static void assertAreEqual(BlobProperties prop1, BlobProperties prop2) {
}
else {
assertNotNull(prop2);
+ assertEquals(prop1.getBlobType(), prop2.getBlobType());
assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobEncryptionTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientEncryptionTests.java
similarity index 99%
rename from microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobEncryptionTests.java
rename to microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientEncryptionTests.java
index e1ce4e4..1fdd8ee 100644
--- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobEncryptionTests.java
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientEncryptionTests.java
@@ -58,18 +58,18 @@
import com.microsoft.azure.storage.core.SR;
@Category({ CloudTests.class, DevFabricTests.class, DevStoreTests.class })
-public class CloudBlobEncryptionTests {
+public class CloudBlobClientEncryptionTests {
protected CloudBlobContainer container;
@Before
- public void blockBlobTestMethodSetup() throws URISyntaxException, StorageException {
+ public void blobEncryptionTestMethodSetup() throws URISyntaxException, StorageException {
this.container = BlobTestHelper.getRandomContainerReference();
this.container.create();
}
@After
- public void blockBlobTestMethodTearDown() throws StorageException {
+ public void blobEncryptionTestMethodTearDown() throws StorageException {
this.container.deleteIfExists();
}
@@ -1010,4 +1010,4 @@ public void eventOccurred(SendingRequestEvent eventArg) {
TestHelper.assertStreamsAreEqualAtIndex(stream, new ByteArrayInputStream(outputStream.toByteArray()), 0, 0,
size, 2 * 1024);
}
-}
+}
\ No newline at end of file
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
index da53166..ecd82ba 100644
--- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
+import java.security.InvalidKeyException;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
@@ -41,6 +42,7 @@
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.SendingRequestEvent;
+import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageEvent;
import com.microsoft.azure.storage.StorageException;
@@ -134,6 +136,86 @@ public void testCloudBlobContainerReference() throws StorageException, URISyntax
.toString());
}
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServer() throws StorageException, URISyntaxException, IOException {
+ this.container.create();
+
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ CloudBlob blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+
+ blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+
+ blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.APPEND_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerSnapshot() throws StorageException, URISyntaxException,
+ IOException {
+ this.container.create();
+
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+ CloudBlob snapshot = blob.createSnapshot();
+ snapshot.getProperties().setContentType("application/octet-stream");
+ snapshot.getProperties().setLength(1024);
+
+ CloudBlob blobRef = this.container.getBlobReferenceFromServer(snapshot.getName(), snapshot.getSnapshotID(),
+ null, null, null);
+ BlobTestHelper.assertAreEqual(snapshot, blobRef);
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerSAS() throws StorageException, URISyntaxException,
+ IOException, InvalidKeyException {
+ this.container.create();
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+
+ SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
+ Calendar now = Calendar.getInstance();
+ now.add(Calendar.MINUTE, 10);
+ policy.setSharedAccessExpiryTime(now.getTime());
+ policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
+ String token = this.container.generateSharedAccessSignature(policy, null);
+
+ CloudBlobContainer containerSAS = new CloudBlobContainer(this.container.getStorageUri(),
+ new StorageCredentialsSharedAccessSignature(token));
+ CloudBlob blobRef = containerSAS.getBlobReferenceFromServer(blob.getName());
+ assertEquals(blob.getClass(), blobRef.getClass());
+ assertEquals(blob.getUri(), blobRef.getUri());
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerMissingBlob() throws StorageException, URISyntaxException,
+ IOException {
+ this.container.create();
+
+ String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("missing");
+
+ try {
+ this.container.getBlobReferenceFromServer(blobName);
+ fail("Get reference from server should fail.");
+ } catch (StorageException ex) {
+ assertEquals(404, ex.getHttpStatusCode());
+ }
+ }
+
/**
* Create a container
*
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobServerEncryptionTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobServerEncryptionTests.java
new file mode 100644
index 0000000..22ff702
--- /dev/null
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobServerEncryptionTests.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.azure.storage.blob;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+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;
+
+@Category({ CloudTests.class, DevFabricTests.class, DevStoreTests.class })
+@Ignore
+/* These test only works on accounts with server-side encryption enabled. */
+public class CloudBlobServerEncryptionTests {
+
+ private CloudBlobContainer container;
+ private CloudBlockBlob blob;
+ private boolean requestFound;
+
+ @Before
+ public void blobEncryptionTestMethodSetup() throws URISyntaxException, StorageException, IOException {
+ this.container = BlobTestHelper.getRandomContainerReference();
+ this.container.create();
+ this.blob = this.container.getBlockBlobReference(BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob"));
+ this.blob.uploadText("test");
+ }
+
+ @After
+ public void blobEncryptionTestMethodTearDown() throws StorageException {
+ this.container.deleteIfExists();
+ }
+
+ @Test
+ public void testBlobAttributesEncryption() throws URISyntaxException, StorageException, IOException {
+ this.blob.downloadAttributes();
+ assertTrue(this.blob.getProperties().isServerEncrypted());
+
+ CloudBlockBlob testBlob = this.container.getBlockBlobReference(this.blob.getName());
+ testBlob.downloadText();
+ assertTrue(testBlob.getProperties().isServerEncrypted());
+ }
+
+ @Test
+ public void testListBlobsEncryption() throws URISyntaxException, StorageException, IOException {
+ boolean blobFound = false;
+
+ for (ListBlobItem b : this.container.listBlobs()) {
+ CloudBlob blob = (CloudBlob) b;
+ assertTrue(blob.getProperties().isServerEncrypted());
+
+ blobFound = true;
+ }
+
+ assertTrue(blobFound);
+ }
+
+ @Test
+ public void testBlobEncryption() 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());
+ CloudBlobServerEncryptionTests.this.requestFound = true;
+ }
+ });
+
+ this.blob.uploadText("test", null, null, null, ctxt);
+ assertTrue(this.requestFound);
+ }
+}
\ No newline at end of file
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 8803324..8797e5a 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java
@@ -536,6 +536,16 @@ public static class HeaderConstants {
*/
public static final String SERVER = "Server";
+ /**
+ * The header that specifies whether a resource is fully encrypted server-side
+ */
+ public static final String SERVER_ENCRYPTED = PREFIX_FOR_STORAGE_HEADER + "server-encrypted";
+
+ /**
+ * The header that acknowledges data used for a write operation is encrypted server-side
+ */
+ public static final String SERVER_REQUEST_ENCRYPTED = PREFIX_FOR_STORAGE_HEADER + "request-server-encrypted";
+
/**
* The header that specifies the snapshot ID.
*/
@@ -581,7 +591,7 @@ public static class HeaderConstants {
/**
* The current storage version header value.
*/
- public static final String TARGET_STORAGE_VERSION = "2015-07-08";
+ public static final String TARGET_STORAGE_VERSION = "2015-12-11";
/**
* The header that specifies the next visible time for a queue message.
@@ -1115,6 +1125,11 @@ public static class QueryConstants {
*/
public static final String PROPERTIES = "Properties";
+ /**
+ * XML element for the server encryption status.
+ */
+ public static final String SERVER_ENCRYPTION_STATUS_ELEMENT = "ServerEncrypted";
+
/**
* XML element for a signed identifier.
*/
@@ -1161,4 +1176,4 @@ public static class QueryConstants {
private Constants() {
// No op
}
-}
+}
\ No newline at end of file
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 00962f7..7997f18 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RequestResult.java
@@ -41,6 +41,11 @@ public final class RequestResult {
* Represents the date header returned by the storage service.
*/
private String requestDate;
+
+ /**
+ * Represents whether or not the data for a write operation is encrypted on the server-side.
+ */
+ private boolean requestServiceEncrypted;
/**
* Represents the ETag header returned by the storage service.
@@ -116,6 +121,15 @@ public Exception getException() {
public String getRequestDate() {
return this.requestDate;
}
+
+ /**
+ * Gets whether the request is server-side encrypted.
+ *
+ * @return A boolean
which contains the server-side encryption status of the request.
+ */
+ public boolean isRequestServiceEncrypted() {
+ return requestServiceEncrypted;
+ }
/**
* Gets the service request ID.
@@ -202,6 +216,16 @@ public void setRequestDate(final String requestDate) {
this.requestDate = requestDate;
}
+ /**
+ * Sets the request's server-encryption status.
+ *
+ * @param requestServiceEncrypted
+ * A boolean
object which represents the server-encryption status to set.
+ */
+ public void setRequestServiceEncrypted(boolean requestServiceEncrypted) {
+ this.requestServiceEncrypted = requestServiceEncrypted;
+ }
+
/**
* Sets the service request ID.
*
@@ -261,4 +285,4 @@ public void setStopDate(final Date stopDate) {
public void setTargetLocation(StorageLocation targetLocation) {
this.targetLocation = targetLocation;
}
-}
+}
\ No newline at end of file
diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java
index 82065d1..d497d1e 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryExponentialRetry.java
@@ -115,11 +115,14 @@ public RetryInfo evaluate(RetryContext retryContext, OperationContext operationC
boolean secondaryNotFound = this.evaluateLastAttemptAndSecondaryNotFound(retryContext);
if (retryContext.getCurrentRetryCount() < this.maximumAttempts) {
+
+ // If this method is called after a successful response, it means
+ // we failed during the response body download. So, we should not
+ // check for success codes here.
int statusCode = retryContext.getLastRequestResult().getStatusCode();
- if ((!secondaryNotFound && statusCode >= 400 && statusCode < 500)
+ if ((!secondaryNotFound && statusCode >= 300 && statusCode < 500 && statusCode != 408)
|| statusCode == HttpURLConnection.HTTP_NOT_IMPLEMENTED
- || statusCode == HttpURLConnection.HTTP_VERSION
- || statusCode == Constants.HeaderConstants.HTTP_UNUSED_306) {
+ || statusCode == HttpURLConnection.HTTP_VERSION) {
return null;
}
diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java
index 474666d..46cc2b5 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/RetryLinearRetry.java
@@ -77,11 +77,14 @@ public RetryInfo evaluate(RetryContext retryContext, OperationContext operationC
boolean secondaryNotFound = this.evaluateLastAttemptAndSecondaryNotFound(retryContext);
if (retryContext.getCurrentRetryCount() < this.maximumAttempts) {
+
+ // If this method is called after a successful response, it means
+ // we failed during the response body download. So, we should not
+ // check for success codes here.
int statusCode = retryContext.getLastRequestResult().getStatusCode();
- if ((!secondaryNotFound && statusCode >= 400 && statusCode < 500)
+ if ((!secondaryNotFound && statusCode >= 300 && statusCode < 500 && statusCode != 408)
|| statusCode == HttpURLConnection.HTTP_NOT_IMPLEMENTED
- || statusCode == HttpURLConnection.HTTP_VERSION
- || statusCode == Constants.HeaderConstants.HTTP_UNUSED_306) {
+ || statusCode == HttpURLConnection.HTTP_VERSION) {
return null;
}
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 c1ada37..d0742ab 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
@@ -272,6 +272,9 @@ else if (Constants.LEASE_DURATION_ELEMENT.equals(currentNode)) {
throw new SAXException(SR.INVALID_RESPONSE_RECEIVED);
}
}
+ else if (Constants.SERVER_ENCRYPTION_STATUS_ELEMENT.equals(currentNode)) {
+ this.properties.setServerEncrypted(Constants.TRUE.equals(value));
+ }
else if (Constants.COPY_ID_ELEMENT.equals(currentNode)) {
if (this.copyState == null) {
this.copyState = new CopyState();
@@ -313,4 +316,4 @@ else if (Constants.COPY_STATUS_DESCRIPTION_ELEMENT.equals(currentNode)) {
this.copyState.setStatusDescription(value);
}
}
-}
+}
\ 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 c6c12da..4369170 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
@@ -107,6 +107,11 @@ public final class BlobProperties {
*/
private Long pageBlobSequenceNumber;
+ /**
+ * Represents the blob's server-side encryption status.
+ */
+ private boolean serverEncrypted;
+
/**
* Creates an instance of the BlobProperties
class.
*/
@@ -122,10 +127,13 @@ public BlobProperties() {
* A BlobProperties
object which represents the blob properties to copy.
*/
public BlobProperties(final BlobProperties other) {
+ this.appendBlobCommittedBlockCount = other.appendBlobCommittedBlockCount;
this.blobType = other.blobType;
+ this.cacheControl = other.cacheControl;
this.contentDisposition = other.contentDisposition;
this.contentEncoding = other.contentEncoding;
this.contentLanguage = other.contentLanguage;
+ this.contentMD5 = other.contentMD5;
this.contentType = other.contentType;
this.copyState = other.copyState;
this.etag = other.etag;
@@ -134,10 +142,8 @@ public BlobProperties(final BlobProperties other) {
this.leaseDuration = other.leaseDuration;
this.length = other.length;
this.lastModified = other.lastModified;
- this.contentMD5 = other.contentMD5;
- this.cacheControl = other.cacheControl;
this.pageBlobSequenceNumber = other.pageBlobSequenceNumber;
- this.appendBlobCommittedBlockCount = other.appendBlobCommittedBlockCount;
+ this.serverEncrypted = other.serverEncrypted;
}
/**
@@ -304,6 +310,15 @@ public Long getPageBlobSequenceNumber() {
return this.pageBlobSequenceNumber;
}
+ /**
+ * Gets the blob's server-side encryption status;
+ *
+ * @return The blob's server-side encryption status.
+ */
+ public boolean isServerEncrypted() {
+ return serverEncrypted;
+ }
+
/**
* Sets the cache control value for the blob.
*
@@ -463,4 +478,14 @@ protected void setLength(final long length) {
protected void setPageBlobSequenceNumber(final Long pageBlobSequenceNumber) {
this.pageBlobSequenceNumber = pageBlobSequenceNumber;
}
-}
+
+ /**
+ * Sets the blob's server-side encryption status.
+ *
+ * @param serverEncrypted
+ * A boolean
which specifies the encryption status to set.
+ */
+ void setServerEncrypted(boolean serverEncrypted) {
+ this.serverEncrypted = serverEncrypted;
+ }
+}
\ No newline at end of file
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 cb3b551..b3d1996 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
@@ -71,6 +71,9 @@ public static BlobAttributes getBlobAttributes(final HttpURLConnection request,
properties.setContentMD5(request.getHeaderField(Constants.HeaderConstants.CONTENT_MD5));
properties.setContentType(request.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE));
properties.setEtag(BaseResponse.getEtag(request));
+
+ properties.setServerEncrypted(
+ Constants.TRUE.equals(request.getHeaderField(Constants.HeaderConstants.SERVER_ENCRYPTED)));
final Calendar lastModifiedCalendar = Calendar.getInstance(Utility.LOCALE_US);
lastModifiedCalendar.setTimeZone(Utility.UTC_ZONE);
@@ -281,4 +284,4 @@ public static String getLeaseTime(final HttpURLConnection request) {
public static String getSnapshotTime(final HttpURLConnection request) {
return request.getHeaderField(Constants.HeaderConstants.SNAPSHOT_ID_HEADER);
}
-}
+}
\ 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 383f822..b39e1ac 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
@@ -303,6 +303,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
blob.getProperties().setLength(0);
return null;
}
@@ -452,7 +453,8 @@ public Long preProcessResponse(CloudAppendBlob blob, CloudBlobClient client, Ope
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
blob.updateCommittedBlockCountFromResponse(this.getConnection());
-
+
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return appendOffset;
}
@@ -1012,4 +1014,4 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) {
this.streamWriteSizeInBytes = streamWriteSizeInBytes;
}
-}
+}
\ No newline at end of file
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 ac2336a..34a1bdd 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
@@ -2748,6 +2748,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
};
@@ -2897,4 +2898,8 @@ 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/CloudBlobContainer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
index 6ec136a..71b5266 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
@@ -926,7 +926,139 @@ public CloudBlobDirectory getDirectoryReference(String directoryName) throws URI
return new CloudBlobDirectory(address, directoryName, this.blobServiceClient, this);
}
+
+
+ /**
+ * Gets a reference to a blob in this container. The blob must already exist on the service.
+ *
+ * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and
+ * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or
+ * getProperties().getBlobType() to determine its subtype.
+ *
+ * @param blobName
+ * A String
that represents the name of the blob.
+ *
+ * @return A {@link CloudBlob} object that represents a reference to the specified blob.
+ *
+ * @throws StorageException
+ * If a storage service error occurred.
+ * @throws URISyntaxException
+ * If the resource URI is invalid.
+ */
+ @DoesServiceRequest
+ public final CloudBlob getBlobReferenceFromServer(final String blobName) throws URISyntaxException, StorageException {
+ return this.getBlobReferenceFromServer(blobName, null, null, null, null);
+ }
+ /**
+ * Gets a reference to a blob in this container. The blob must already exist on the service.
+ *
+ * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and
+ * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or
+ * getProperties().getBlobType() to determine its subtype.
+ *
+ * @param blobName
+ * A String
that represents the name of the blob.
+ * @param snapshotID
+ * A String
that represents the snapshot ID 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.
+ *
+ * @return A {@link CloudBlob} object that represents a reference to the specified blob.
+ *
+ * @throws StorageException
+ * If a storage service error occurred.
+ * @throws URISyntaxException
+ * If the resource URI is invalid.
+ */
+ @DoesServiceRequest
+ public final CloudBlob getBlobReferenceFromServer(final String blobName, final String snapshotID,
+ AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
+ throws URISyntaxException, StorageException {
+ if (opContext == null) {
+ opContext = new OperationContext();
+ }
+ options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);
+
+ StorageUri blobUri = PathUtility.appendPathToUri(this.getStorageUri(), blobName);
+ return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
+ this.getBlobReferenceFromServerImpl(blobName, blobUri, snapshotID, accessCondition, options), options.getRetryPolicyFactory(), opContext);
+ }
+
+ private StorageRequest getBlobReferenceFromServerImpl(
+ final String blobName, final StorageUri blobUri, final String snapshotID, final AccessCondition accessCondition,
+ final BlobRequestOptions options) {
+ final StorageRequest getRequest = new StorageRequest(
+ options, this.getStorageUri()) {
+
+ @Override
+ public void setRequestLocationMode() {
+ this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY);
+ }
+
+ @Override
+ public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
+ OperationContext context) throws Exception {
+ StorageUri transformedblobUri = CloudBlobContainer.this.getServiceClient().getCredentials().transformUri(blobUri, context);
+ return BlobRequest.getBlobProperties(transformedblobUri.getUri(this.getCurrentLocation()), options, context, accessCondition, snapshotID);
+ }
+
+ @Override
+ public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
+ throws Exception {
+ StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
+ }
+
+ @Override
+ public CloudBlob preProcessResponse(CloudBlobContainer container, CloudBlobClient client, OperationContext context)
+ throws Exception {
+ if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
+ this.setNonExceptionedRetryableFailure(true);
+ return null;
+ }
+
+ // Set attributes
+ final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
+ blobUri, snapshotID);
+
+ CloudBlob blob;
+ switch (retrievedAttributes.getProperties().getBlobType()) {
+ case APPEND_BLOB:
+ blob = container.getAppendBlobReference(blobName, snapshotID);
+ break;
+
+ case BLOCK_BLOB:
+ blob = container.getBlockBlobReference(blobName, snapshotID);
+ break;
+
+ case PAGE_BLOB:
+ blob = container.getPageBlobReference(blobName, snapshotID);
+ break;
+
+ default:
+ throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE,
+ SR.INVALID_RESPONSE_RECEIVED, Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
+ }
+
+ blob.properties = retrievedAttributes.getProperties();
+ blob.metadata = retrievedAttributes.getMetadata();
+
+ return blob;
+ }
+ };
+
+ return getRequest;
+ }
+
+
/**
* Returns the metadata for the container. This value is initialized with the metadata from the queue by a call to
* {@link #downloadAttributes}, and is set on the queue with a call to {@link #uploadMetadata}.
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 d4e1fce..5d4b98a 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
@@ -380,6 +380,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
@@ -797,6 +798,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
@@ -990,6 +992,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
return null;
}
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
@@ -1111,4 +1114,4 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) {
this.streamWriteSizeInBytes = streamWriteSizeInBytes;
}
-}
+}
\ No newline at end of file
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 4022a59..a2429aa 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
@@ -382,6 +382,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
blob.getProperties().setLength(length);
return null;
}
@@ -922,6 +923,7 @@ public Void preProcessResponse(CloudPageBlob blob, CloudBlobClient client, Opera
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
blob.updateSequenceNumberFromResponse(this.getConnection());
+ this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
};
@@ -1236,4 +1238,4 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) {
this.streamWriteSizeInBytes = streamWriteSizeInBytes;
}
-}
+}
\ No newline at end of file