Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
2013.1.18 Version 0.4.0
2013.02.22 Version 0.4.1
* Fixed the return value of BlobInputStream.read
* Fixed CloudPageBlob.downloadPageRanges to retrieve the blob length
* Fixed MD5 validation in BlobInputStream
* Return ETag in TableResult not only for Insert but also for other operations

2013.01.18 Version 0.4.0
* Added support for Windows Azure Media Services
* Updated dependencies to non-beta stable versions
* Add a Sending Request Event to OperationContext in Storage Client code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public final class BlobInputStream extends InputStream {
*/
private boolean validateBlobMd5;

/**
* Holds the Blob MD5.
*/
private final String retrievedContentMD5Value;

/**
* Holds the reference to the current buffered data.
*/
Expand Down Expand Up @@ -161,11 +166,11 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce

final HttpURLConnection attributesRequest = this.opContext.getCurrentRequestObject();

final String retrievedContentMD5Value = attributesRequest.getHeaderField(Constants.HeaderConstants.CONTENT_MD5);
this.retrievedContentMD5Value = attributesRequest.getHeaderField(Constants.HeaderConstants.CONTENT_MD5);

// Will validate it if it was returned
this.validateBlobMd5 = !options.getDisableContentMD5Validation()
&& !Utility.isNullOrEmpty(retrievedContentMD5Value);
&& !Utility.isNullOrEmpty(this.retrievedContentMD5Value);

// Validates the first option, and sets future requests to use if match
// request option.
Expand Down Expand Up @@ -395,8 +400,17 @@ public boolean markSupported() {
@DoesServiceRequest
public int read() throws IOException {
final byte[] tBuff = new byte[1];
this.read(tBuff, 0, 1);
return tBuff[0];
final int numberOfBytesRead = this.read(tBuff, 0, 1);

if (numberOfBytesRead > 0) {
return tBuff[0] & 0xFF;
}
else if (numberOfBytesRead == 0) {
throw new IOException("Unexpected error. Stream returned unexpected number of bytes.");
}
else {
return -1;
}
}

/**
Expand Down Expand Up @@ -519,13 +533,13 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
if (this.currentAbsoluteReadPosition == this.streamLength) {
// Reached end of stream, validate md5.
final String calculatedMd5 = Base64.encode(this.md5Digest.digest());
if (!calculatedMd5.equals(this.parentBlobRef.getProperties().getContentMD5())) {
if (!calculatedMd5.equals(this.retrievedContentMD5Value)) {
this.lastError = Utility
.initIOException(new StorageException(
StorageErrorCodeStrings.INVALID_MD5,
String.format(
"Blob data corrupted (integrity check failed), Expected value is %s, retrieved %s",
this.parentBlobRef.getProperties().getContentMD5(), calculatedMd5),
this.retrievedContentMD5Value, calculatedMd5),
Constants.HeaderConstants.HTTP_UNUSED_306, null, null));
this.streamFaulted = true;
throw this.lastError;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ protected CloudBlob(final CloudBlob otherBlob) {
*
* @param leaseTimeInSeconds
* Specifies the span of time for which to acquire the lease, in seconds.
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* zero.
*
* @param proposedLeaseId
Expand All @@ -245,7 +245,7 @@ public final String acquireLease(final Integer leaseTimeInSeconds, final String
*
* @param leaseTimeInSeconds
* Specifies the span of time for which to acquire the lease, in seconds.
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* zero.
*
* @param proposedLeaseId
Expand All @@ -254,12 +254,12 @@ public final String acquireLease(final Integer leaseTimeInSeconds, final String
*
* @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
* <code>null</code> will use the default request options from the associated service client
* <code>null</code> 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. The context
* is used to track requests to the storage service, and to provide additional runtime information about
Expand Down Expand Up @@ -342,7 +342,7 @@ protected final void assertCorrectBlobType() throws StorageException {
}

/**
* Breaks the lease and ensures that another client cannot acquire a new lease until the current lease period
* Breaks the lease and ensures that another client cannot acquire a new lease until the current lease period
* has expired.
*
* @param breakPeriodInSeconds
Expand All @@ -360,7 +360,7 @@ public final long breakLease(final Integer breakPeriodInSeconds) throws StorageE
}

/**
* Breaks the existing lease, using the specified request options and operation context, and ensures that another
* Breaks the existing lease, using the specified request options and operation context, and ensures that another
* client cannot acquire a new lease until the current lease period has expired.
*
* @param breakPeriodInSeconds
Expand All @@ -371,7 +371,7 @@ public final long breakLease(final Integer breakPeriodInSeconds) throws StorageE
* 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
* <code>null</code> will use the default request options from the associated service client
* <code>null</code> 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. The context
Expand Down Expand Up @@ -1328,14 +1328,16 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op
return null;
}

// Do not update blob length in downloadRangeInternal API.
final long orignalBlobLength = blob.properties.getLength();
// Do not update blob length and Content-MD5 in downloadRangeInternal API.
final long originalBlobLength = blob.properties.getLength();
final String originalContentMD5 = blob.properties.getContentMD5();
final BlobAttributes retrievedAttributes = BlobResponse.getAttributes(request, blob.getUri(),
blob.snapshotID, opContext);
blob.properties = retrievedAttributes.getProperties();
blob.metadata = retrievedAttributes.getMetadata();
blob.copyState = retrievedAttributes.getCopyState();
blob.properties.setLength(orignalBlobLength);
blob.properties.setContentMD5(originalContentMD5);
blob.properties.setLength(originalBlobLength);

final String contentLength = request.getHeaderField(Constants.HeaderConstants.CONTENT_LENGTH);
final long expectedLength = Long.parseLong(contentLength);
Expand Down Expand Up @@ -2049,7 +2051,7 @@ public final void changeLease(final String proposedLeaseId, final AccessConditio
* required to be set with an access condition.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* <code>null</code> will use the default request options from the associated service client
* <code>null</code> 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. The context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ public ArrayList<PageRange> execute(final CloudBlobClient client, final CloudBlo
}

blob.updateEtagAndLastModifiedFromResponse(request);
blob.updateLengthFromResponse(request);

final GetPageRangesResponse response = new GetPageRangesResponse(request.getInputStream());
return response.getPageRanges();
}
Expand Down Expand Up @@ -490,7 +492,6 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op
}

blob.updateEtagAndLastModifiedFromResponse(request);
blob.updateLengthFromResponse(request);
return null;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ public static class HeaderConstants {
/**
* Specifies the value to use for UserAgent header.
*/
public static final String USER_AGENT_VERSION = "Client v0.1.3.1";
public static final String USER_AGENT_VERSION = "Client v0.1.3.2";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space is the separator in user agent string which may not be the intention of your usage.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,9 @@ private TableResult performDelete(final CloudTableClient client, final String ta
public TableResult execute(final CloudTableClient client, final TableOperation operation,
final OperationContext opContext) throws Exception {

final HttpURLConnection request = TableRequest.delete(client.getTransformedEndPoint(opContext), tableName,
generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity().getEtag(),
options.getTimeoutIntervalInMs(), null, options, opContext);
final HttpURLConnection request = TableRequest.delete(client.getTransformedEndPoint(opContext),
tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity()
.getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext);

client.getCredentials().signRequestLite(request, -1L, opContext);

Expand Down Expand Up @@ -323,8 +323,8 @@ private TableResult performInsert(final CloudTableClient client, final String ta
@Override
public TableResult execute(final CloudTableClient client, final TableOperation operation,
final OperationContext opContext) throws Exception {
final HttpURLConnection request = TableRequest.insert(client.getTransformedEndPoint(opContext), tableName,
generateRequestIdentity(isTableEntry, tableIdentity, false),
final HttpURLConnection request = TableRequest.insert(client.getTransformedEndPoint(opContext),
tableName, generateRequestIdentity(isTableEntry, tableIdentity, false),
operation.opType != TableOperationType.INSERT ? operation.getEntity().getEtag() : null,
operation.opType.getUpdateType(), options.getTimeoutIntervalInMs(), null, options, opContext);

Expand Down Expand Up @@ -410,8 +410,8 @@ private TableResult performMerge(final CloudTableClient client, final String tab
public TableResult execute(final CloudTableClient client, final TableOperation operation,
final OperationContext opContext) throws Exception {

final HttpURLConnection request = TableRequest.merge(client.getTransformedEndPoint(opContext), tableName,
generateRequestIdentity(false, null, false), operation.getEntity().getEtag(),
final HttpURLConnection request = TableRequest.merge(client.getTransformedEndPoint(opContext),
tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(),
options.getTimeoutIntervalInMs(), null, options, opContext);

client.getCredentials().signRequestLite(request, -1L, opContext);
Expand Down Expand Up @@ -476,8 +476,8 @@ private TableResult performUpdate(final CloudTableClient client, final String ta
public TableResult execute(final CloudTableClient client, final TableOperation operation,
final OperationContext opContext) throws Exception {

final HttpURLConnection request = TableRequest.update(client.getTransformedEndPoint(opContext), tableName,
generateRequestIdentity(false, null, false), operation.getEntity().getEtag(),
final HttpURLConnection request = TableRequest.update(client.getTransformedEndPoint(opContext),
tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(),
options.getTimeoutIntervalInMs(), null, options, opContext);

client.getCredentials().signRequestLite(request, -1L, opContext);
Expand Down Expand Up @@ -686,7 +686,8 @@ protected TableResult parseResponse(final XMLStreamReader xmlr, final int httpSt
resObj = new TableResult(httpStatusCode);
resObj.setResult(this.getEntity());

if (this.opType != TableOperationType.DELETE) {
if (this.opType != TableOperationType.DELETE && etagFromHeader != null) {
resObj.setEtag(etagFromHeader);
this.getEntity().setEtag(etagFromHeader);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,43 @@ public void eventOccurred(SendingRequestEvent eventArg) {
}
}

@Test
public void testBlobInputStream() throws URISyntaxException, StorageException, IOException {
final int blobLength = 16 * 1024;
final Random randGenerator = new Random();
String blobName = "testblob" + Integer.toString(randGenerator.nextInt(50000));
blobName = blobName.replace('-', '_');

final CloudBlobContainer containerRef = bClient.getContainerReference(BlobTestBase.testSuiteContainerName);

final CloudBlockBlob blobRef = containerRef.getBlockBlobReference(blobName);

final byte[] buff = new byte[blobLength];
randGenerator.nextBytes(buff);
buff[0] = -1;
buff[1] = -128;
final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);

final BlobRequestOptions options = new BlobRequestOptions();
final OperationContext operationContext = new OperationContext();
options.setStoreBlobContentMD5(true);
options.setTimeoutIntervalInMs(90000);
options.setRetryPolicyFactory(new RetryNoRetry());
blobRef.uploadFullBlob(sourceStream, blobLength, null, options, operationContext);

BlobInputStream blobStream = blobRef.openInputStream();

for (int i = 0; i < blobLength; i++) {
int data = blobStream.read();
Assert.assertTrue(data >= 0);
Assert.assertEquals(buff[i], (byte) data);
}

Assert.assertEquals(-1, blobStream.read());

blobRef.delete();
}

@Test
public void testCurrentOperationByteCount() throws URISyntaxException, StorageException, IOException {
final int blockLength = 4 * 1024 * 1024;
Expand Down Expand Up @@ -750,6 +787,7 @@ public void testCurrentOperationByteCount() throws URISyntaxException, StorageEx
BlobRequestOptions options = new BlobRequestOptions();
options.setTimeoutIntervalInMs(2000);
options.setRetryPolicyFactory(new RetryNoRetry());

ByteArrayOutputStream downloadedDataStream = new ByteArrayOutputStream();
try {
blobRef.download(downloadedDataStream, null, options, operationContext);
Expand All @@ -769,5 +807,4 @@ public void testCurrentOperationByteCount() throws URISyntaxException, StorageEx

blobRef.delete();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,8 @@ public void testPeekMessagesFromEmptyQueue() throws URISyntaxException, StorageE
@Test
public void testUpdateMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException {

queue.clear();

String messageContent = "messagetest";
CloudQueueMessage message1 = new CloudQueueMessage(messageContent);
queue.addMessage(message1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public void insertOrMerge() throws StorageException {
TableResult insertResult = tClient.execute(testSuiteTableName, TableOperation.insertOrMerge(baseEntity));

Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
Assert.assertNotNull(insertResult.getEtag());

// Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE
tClient.execute(testSuiteTableName, TableOperation.insertOrMerge(secondEntity));
Expand Down Expand Up @@ -206,6 +207,7 @@ public void insertOrReplace() throws StorageException {
TableResult insertResult = tClient.execute(testSuiteTableName, TableOperation.insertOrReplace(baseEntity));

Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
Assert.assertNotNull(insertResult.getEtag());

// Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE
tClient.execute(testSuiteTableName, TableOperation.insertOrReplace(secondEntity));
Expand Down Expand Up @@ -259,7 +261,10 @@ public void merge() throws StorageException {
secondEntity.setRowKey(baseEntity.getRowKey());
secondEntity.setEtag(baseEntity.getEtag());

tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity));
TableResult mergeResult = tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity));

Assert.assertEquals(mergeResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
Assert.assertNotNull(mergeResult.getEtag());

TableResult res2 = tClient.execute(testSuiteTableName, TableOperation.retrieve(secondEntity.getPartitionKey(),
secondEntity.getRowKey(), DynamicTableEntity.class));
Expand Down Expand Up @@ -456,7 +461,10 @@ public void replace() throws StorageException {
// Remove property and update
retrievedEntity.getProperties().remove("D");

tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity));
TableResult replaceResult = tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity));

Assert.assertEquals(replaceResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
Assert.assertNotNull(replaceResult.getEtag());

// Retrieve Entity
queryResult = tClient
Expand Down