diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java new file mode 100644 index 000000000000..66313bc04ebf --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.client; + +import java.io.InputStream; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class BlobAccessPolicyResponse extends AccessPolicyResponseBase { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public BlobAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessBlobPolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessBlobPolicy retPolicy = new SharedAccessBlobPolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessBlobPolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java index 90d9f6b2d0ab..f0f782200ab7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java @@ -28,6 +28,11 @@ final class BlobAttributes { */ private HashMap metadata; + /** + * Represents the state of the most recent or pending copy operation. + */ + private CopyState copyState; + /** * Holds the properties of the blob. */ @@ -55,6 +60,10 @@ public HashMap getMetadata() { return this.metadata; } + public CopyState getCopyState() { + return this.copyState; + } + public BlobProperties getProperties() { return this.properties; } @@ -66,4 +75,8 @@ protected void setMetadata(final HashMap metadata) { protected void setProperties(final BlobProperties properties) { this.properties = properties; } + + public void setCopyState(final CopyState copyState) { + this.copyState = copyState; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java index b1cca6aed131..8f3acd35234b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java @@ -18,56 +18,6 @@ * Holds the Constants used for the Queue Service. */ final class BlobConstants { - /** - * Defines constants for use with query strings. - */ - public static class QueryConstants { - /** - * The query component for the SAS signature. - */ - public static final String SIGNATURE = "sig"; - - /** - * The query component for the signed SAS expiry time. - */ - public static final String SIGNED_EXPIRY = "se"; - - /** - * The query component for the signed SAS identifier. - */ - public static final String SIGNED_IDENTIFIER = "si"; - - /** - * The query component for the signed SAS permissions. - */ - public static final String SIGNED_PERMISSIONS = "sp"; - - /** - * The query component for the signed SAS resource. - */ - public static final String SIGNED_RESOURCE = "sr"; - - /** - * The query component for the signed SAS start time. - */ - public static final String SIGNED_START = "st"; - - /** - * The query component for the signed SAS version. - */ - public static final String SIGNED_VERSION = "sv"; - - /** - * The query component for snapshot time. - */ - public static final String SNAPSHOT = "snapshot"; - } - - /** - * XML element for an access policy. - */ - public static final String ACCESS_POLICY = "AccessPolicy"; - /** * XML element for authentication error details. */ @@ -204,11 +154,6 @@ public static class QueryConstants { */ public static final int DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES = 4 * com.microsoft.windowsazure.services.core.storage.Constants.MB; - /** - * XML element for the end time of an access policy. - */ - public static final String EXPIRY = "Expiry"; - /** * Specifies snapshots are to be included. */ @@ -219,11 +164,6 @@ public static class QueryConstants { */ public static final String LATEST_ELEMENT = "Latest"; - /** - * Maximum number of shared access policy identifiers supported by server. - */ - public static final int MAX_SHARED_ACCESS_POLICY_IDENTIFIERS = 5; - /** * The maximum size, in bytes, of a blob before it must be separated into blocks */ @@ -261,11 +201,6 @@ public static class QueryConstants { public static final String PAGE_WRITE = com.microsoft.windowsazure.services.core.storage.Constants.PREFIX_FOR_STORAGE_HEADER + "page-write"; - /** - * XML element for the permission of an access policy. - */ - public static final String PERMISSION = "Permission"; - /** * XML element for properties. */ @@ -277,16 +212,6 @@ public static class QueryConstants { public static final String SEQUENCE_NUMBER = com.microsoft.windowsazure.services.core.storage.Constants.PREFIX_FOR_STORAGE_HEADER + "blob-sequence-number"; - /** - * XML element for a signed identifier. - */ - public static final String SIGNED_IDENTIFIER_ELEMENT = "SignedIdentifier"; - - /** - * XML element for signed identifiers. - */ - public static final String SIGNED_IDENTIFIERS_ELEMENT = "SignedIdentifiers"; - /** * The header for the blob content length. */ @@ -319,11 +244,6 @@ public static class QueryConstants { */ public static final String SNAPSHOTS_ONLY_VALUE = "only"; - /** - * XML element for the start time of an access policy. - */ - public static final String START = "Start"; - /** * XML element for page range start elements. */ @@ -339,6 +259,16 @@ public static class QueryConstants { */ public static final String UNCOMMITTED_ELEMENT = "Uncommitted"; + /** + * The default timeout of a copy operation. + */ + public static final int DEFAULT_COPY_TIMEOUT_IN_SECONDS = 3600; + + /** + * The default polling interval of a copy operation. + */ + public static final int DEFAULT_POLLING_INTERVAL_IN_SECONDS = 30; + /** * Private Default Ctor */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java index 15b2cc0065d4..c5eabded3812 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java @@ -55,14 +55,14 @@ public final class BlobContainerPermissions { /** * Gets the set of shared access policies for the container. */ - private HashMap sharedAccessPolicies; + private HashMap sharedAccessPolicies; /** * Creates an instance of the BlobContainerPermissions class. */ public BlobContainerPermissions() { this.setPublicAccess(BlobContainerPublicAccessType.OFF); - this.sharedAccessPolicies = new HashMap(); + this.sharedAccessPolicies = new HashMap(); } /** @@ -75,10 +75,10 @@ public BlobContainerPublicAccessType getPublicAccess() { /** * Returns the set of shared access policies for the container. * - * @return A HashMap object of {@link SharedAccessPolicy} objects that represent the set of shared + * @return A HashMap object of {@link SharedAccessBlobPolicy} objects that represent the set of shared * access policies for the container. */ - public HashMap getSharedAccessPolicies() { + public HashMap getSharedAccessPolicies() { return this.sharedAccessPolicies; } @@ -94,7 +94,7 @@ public void setPublicAccess(final BlobContainerPublicAccessType publicAccess) { * @param sharedAccessPolicies * the sharedAccessPolicies to set */ - public void setSharedAccessPolicies(final HashMap sharedAccessPolicies) { + public void setSharedAccessPolicies(final HashMap sharedAccessPolicies) { this.sharedAccessPolicies = sharedAccessPolicies; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java index 412aa9437158..4b34574466fd 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java @@ -17,6 +17,9 @@ import java.util.Date; import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; +import com.microsoft.windowsazure.services.core.storage.LeaseStatus; /** * Represents the system properties for a container. @@ -39,6 +42,21 @@ public final class BlobContainerProperties { */ private Date lastModified; + /** + * Represents the container's lease status. + */ + private LeaseStatus leaseStatus; + + /** + * Represents the container's lease state. + */ + private LeaseState leaseState; + + /** + * Represents the container's lease duration. + */ + private LeaseDuration leaseDuration; + /** * @return the etag */ @@ -53,6 +71,27 @@ public Date getLastModified() { return this.lastModified; } + /** + * @return the leaseStatus + */ + public LeaseStatus getLeaseStatus() { + return this.leaseStatus; + } + + /** + * @return the leaseState + */ + public LeaseState getLeaseState() { + return this.leaseState; + } + + /** + * @return the leaseDuration + */ + public LeaseDuration getLeaseDuration() { + return this.leaseDuration; + } + /** * @param etag * the etag to set @@ -68,4 +107,34 @@ public void setEtag(final String etag) { public void setLastModified(final Date lastModified) { this.lastModified = lastModified; } + + /** + * Reserved for internal use. + * + * @param leaseStatus + * the leaseStatus to set + */ + public void setLeaseStatus(final LeaseStatus leaseStatus) { + this.leaseStatus = leaseStatus; + } + + /** + * Reserved for internal use. + * + * @param LeaseState + * the LeaseState to set + */ + public void setLeaseState(final LeaseState leaseState) { + this.leaseState = leaseState; + } + + /** + * Reserved for internal use. + * + * @param LeaseDuration + * the LeaseDuration to set + */ + public void setLeaseDuration(final LeaseDuration leaseDuration) { + this.leaseDuration = leaseDuration; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java index f10adc792dd3..10fda315c1fb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java @@ -25,6 +25,8 @@ import javax.xml.stream.XMLStreamReader; import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; import com.microsoft.windowsazure.services.core.storage.LeaseStatus; import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; @@ -61,6 +63,7 @@ protected static CloudBlob readBlob(final XMLStreamReader xmlr, final CloudBlobC String urlString = null; HashMap metadata = null; BlobProperties properties = null; + CopyState copyState = null; int eventType = xmlr.getEventType(); // check if there are more events in the input stream @@ -86,6 +89,49 @@ else if (name.equals(Constants.METADATA_ELEMENT)) { metadata = DeserializationHelper.parseMetadateFromXML(xmlr); xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.METADATA_ELEMENT); } + else if (name.equals(Constants.COPY_ID_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setCopyId(Utility.readElementFromXMLReader(xmlr, Constants.COPY_ID_ELEMENT)); + } + else if (name.equals(Constants.COPY_COMPLETION_TIME_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(Utility + .readElementFromXMLReader(xmlr, Constants.COPY_COMPLETION_TIME_ELEMENT))); + } + else if (name.equals(Constants.COPY_STATUS_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setStatus(CopyStatus.parse(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_ELEMENT))); + } + else if (name.equals(Constants.COPY_SOURCE_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setSource(new URI(Utility.readElementFromXMLReader(xmlr, Constants.COPY_SOURCE_ELEMENT))); + } + else if (name.equals(Constants.COPY_PROGRESS_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.COPY_PROGRESS_ELEMENT); + String[] progressSequence = tempString.split("/"); + copyState.setBytesCopied(Long.parseLong(progressSequence[0])); + copyState.setTotalBytes(Long.parseLong(progressSequence[1])); + } + else if (name.equals(Constants.COPY_STATUS_DESCRIPTION_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setStatusDescription(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_DESCRIPTION_ELEMENT)); + } } else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstants.BLOB_ELEMENT)) { break; @@ -125,6 +171,7 @@ else if (properties.getBlobType() == BlobType.PAGE_BLOB) { retBlob.snapshotID = snapshotID; retBlob.properties = properties; retBlob.metadata = metadata; + retBlob.copyState = copyState; return retBlob; } else { @@ -268,6 +315,18 @@ protected static BlobContainerProperties readBlobContainerProperties(final XMLSt else if (name.equals(Constants.ETAG_ELEMENT)) { properties.setEtag(Utility.readElementFromXMLReader(xmlr, Constants.ETAG_ELEMENT)); } + else if (name.equals(Constants.LEASE_STATUS_ELEMENT)) { + properties.setLeaseStatus(LeaseStatus.parse(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_ELEMENT))); + } + else if (name.equals(Constants.LEASE_STATE_ELEMENT)) { + properties.setLeaseState(LeaseState.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_STATE_ELEMENT))); + } + else if (name.equals(Constants.LEASE_DURATION_ELEMENT)) { + properties.setLeaseDuration(LeaseDuration.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_DURATION_ELEMENT))); + } } else { // expect end of properties @@ -343,9 +402,10 @@ else if (name.equals(BlobConstants.BLOB_PREFIX_ELEMENT)) { * @throws ParseException * if a date value is not correctly encoded * @throws StorageException + * @throws URISyntaxException */ protected static BlobProperties readBlobProperties(final XMLStreamReader xmlr) throws XMLStreamException, - ParseException, StorageException { + ParseException, StorageException, URISyntaxException { xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.PROPERTIES); int eventType = xmlr.getEventType(); final BlobProperties properties = new BlobProperties(); @@ -422,6 +482,14 @@ else if (tempString.equals(Constants.UNLOCKED_VALUE.toLowerCase())) { Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } } + else if (name.equals(Constants.LEASE_STATE_ELEMENT)) { + properties.setLeaseState(LeaseState.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_STATE_ELEMENT))); + } + else if (name.equals(Constants.LEASE_DURATION_ELEMENT)) { + properties.setLeaseDuration(LeaseDuration.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_DURATION_ELEMENT))); + } } else if (eventType == XMLStreamConstants.END_ELEMENT) { // expect end of properties diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java index 7e8f7ddf660e..81c3faae35c5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java @@ -17,6 +17,8 @@ import java.util.Date; import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; import com.microsoft.windowsazure.services.core.storage.LeaseStatus; /** @@ -78,6 +80,16 @@ public final class BlobProperties { */ private LeaseStatus leaseStatus = com.microsoft.windowsazure.services.core.storage.LeaseStatus.UNLOCKED; + /** + * Represents the blob's lease state. + */ + private LeaseState leaseState; + + /** + * Represents the blob's lease duration. + */ + private LeaseDuration leaseDuration; + /** * Represents the size, in bytes, of the blob. */ @@ -104,6 +116,8 @@ public BlobProperties(final BlobProperties other) { this.contentType = other.contentType; this.etag = other.etag; this.leaseStatus = other.leaseStatus; + this.leaseState = other.leaseState; + this.leaseDuration = other.leaseDuration; this.length = other.length; this.lastModified = other.lastModified; this.contentMD5 = other.contentMD5; @@ -184,6 +198,20 @@ public LeaseStatus getLeaseStatus() { return this.leaseStatus; } + /** + * @return the leaseState + */ + public LeaseState getLeaseState() { + return this.leaseState; + } + + /** + * @return the leaseDuration + */ + public LeaseDuration getLeaseDuration() { + return this.leaseDuration; + } + /** * @return the length */ @@ -271,6 +299,26 @@ public void setLeaseStatus(final LeaseStatus leaseStatus) { this.leaseStatus = leaseStatus; } + /** + * Reserved for internal use. + * + * @param LeaseState + * the LeaseState to set + */ + public void setLeaseState(final LeaseState leaseState) { + this.leaseState = leaseState; + } + + /** + * Reserved for internal use. + * + * @param LeaseDuration + * the LeaseDuration to set + */ + public void setLeaseDuration(final LeaseDuration leaseDuration) { + this.leaseDuration = leaseDuration; + } + /** * Reserved for internal use. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java index a328318fcdb6..52e9cca26ae6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java @@ -122,6 +122,52 @@ public static HttpURLConnection copyFrom(final URI uri, final int timeout, Strin return request; } + /** + * Generates a web request to abort a copy operation. + * + * @param uri + * The absolute URI to the container. + * @param timeout + * The server timeout interval. + * @param copyId + * A String object that identifying the copy operation. + * @param accessCondition + * The access condition to apply to the request. Only lease conditions are supported for this operation. + * @param blobOptions + * the options to use for the request. + * @param opContext + * a tracking object for the request + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public static HttpURLConnection abortCopy(final URI uri, final int timeout, final String copyId, + final AccessCondition accessCondition, final BlobRequestOptions blobOptions, + final OperationContext opContext) throws StorageException, IOException, URISyntaxException { + + final UriQueryBuilder builder = new UriQueryBuilder(); + + builder.add("comp", "copy"); + builder.add("copyid", copyId); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod("PUT"); + + request.setRequestProperty("x-ms-copy-action", "abort"); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request, true); + } + + return request; + } + /** * Creates the web request. * @@ -431,6 +477,19 @@ public static HttpURLConnection getProperties(final URI uri, final int timeout, * The server timeout interval * @param action * the LeaseAction to perform + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param blobOptions @@ -447,6 +506,7 @@ public static HttpURLConnection getProperties(final URI uri, final int timeout, * @throws IllegalArgumentException */ public static HttpURLConnection lease(final URI uri, final int timeout, final LeaseAction action, + final Integer leaseTimeInSeconds, final String proposedLeaseId, final Integer breakPeriodInSeconds, final AccessCondition accessCondition, final BlobRequestOptions blobOptions, final OperationContext opContext) throws IOException, URISyntaxException, StorageException { @@ -461,6 +521,17 @@ public static HttpURLConnection lease(final URI uri, final int timeout, final Le request.setFixedLengthStreamingMode(0); request.setRequestProperty("x-ms-lease-action", action.toString()); + if (leaseTimeInSeconds != null) { + request.setRequestProperty("x-ms-lease-duration", leaseTimeInSeconds.toString()); + } + else { + request.setRequestProperty("x-ms-lease-duration", "-1"); + } + + if (proposedLeaseId != null) { + request.setRequestProperty("x-ms-proposed-lease-id", proposedLeaseId); + } + if (accessCondition != null) { accessCondition.applyConditionToRequest(request); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java index 6871da71e97b..ea4f9ab2f974 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java @@ -16,6 +16,8 @@ import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; import java.util.Calendar; import java.util.Date; @@ -41,9 +43,11 @@ final class BlobResponse extends BaseResponse { * @param opContext * a tracking object for the request * @return the BlobAttributes from the given request + * @throws ParseException + * @throws URISyntaxException */ public static BlobAttributes getAttributes(final HttpURLConnection request, final URI resourceURI, - final String snapshotID, final OperationContext opContext) { + final String snapshotID, final OperationContext opContext) throws URISyntaxException, ParseException { final String blobType = request.getHeaderField(BlobConstants.BLOB_TYPE_HEADER); final BlobAttributes attributes = new BlobAttributes(BlobType.parse(blobType)); @@ -61,10 +65,9 @@ public static BlobAttributes getAttributes(final HttpURLConnection request, fina lastModifiedCalendar.setTime(new Date(request.getLastModified())); properties.setLastModified(lastModifiedCalendar.getTime()); - final String leaseStatus = request.getHeaderField(Constants.HeaderConstants.LEASE_STATUS); - if (!Utility.isNullOrEmpty(leaseStatus)) { - properties.setLeaseStatus(com.microsoft.windowsazure.services.core.storage.LeaseStatus.parse(leaseStatus)); - } + properties.setLeaseStatus(BaseResponse.getLeaseStatus(request)); + properties.setLeaseState(BaseResponse.getLeaseState(request)); + properties.setLeaseDuration(BaseResponse.getLeaseDuration(request)); final String rangeHeader = request.getHeaderField(Constants.HeaderConstants.CONTENT_RANGE); final String xContentLengthHeader = request.getHeaderField(BlobConstants.CONTENT_LENGTH_HEADER); @@ -89,6 +92,8 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { attributes.snapshotID = snapshotID; attributes.setMetadata(getMetadata(request)); + + attributes.setCopyState(BaseResponse.getCopyState(request)); return attributes; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java index e108358441b4..a434b75f3de7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java @@ -27,6 +27,7 @@ import java.util.Date; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; @@ -43,6 +44,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.StreamMd5AndLength; import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseResponse; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -73,6 +75,11 @@ public abstract class CloudBlob implements ListBlobItem { */ String snapshotID; + /** + * Represents the state of the most recent or pending copy operation. + */ + CopyState copyState; + /** * Holds the Blobs container Reference. */ @@ -122,7 +129,6 @@ protected CloudBlob(final BlobType type, final URI uri, final CloudBlobClient cl this(type); Utility.assertNotNull("blobAbsoluteUri", uri); - Utility.assertNotNull("serviceClient", client); this.blobServiceClient = client; this.uri = uri; @@ -207,24 +213,42 @@ protected CloudBlob(final CloudBlob otherBlob) { this.parent = otherBlob.parent; this.blobServiceClient = otherBlob.blobServiceClient; this.name = otherBlob.name; + this.copyState = otherBlob.copyState; } /** * Acquires a new lease on the blob. * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * * @return A String that represents the lease ID. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public final String acquireLease() throws StorageException { - return this.acquireLease(null, null, null); + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId) + throws StorageException { + return this.acquireLease(leaseTimeInSeconds, proposedLeaseId, null, null, null); } /** * Acquires a new lease on the blob using the specified request options and operation context. * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param options @@ -242,8 +266,9 @@ public final String acquireLease() throws StorageException { * If a storage service error occurred. */ @DoesServiceRequest - public final String acquireLease(final AccessCondition accessCondition, BlobRequestOptions options, - OperationContext opContext) throws StorageException { + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -263,8 +288,8 @@ public String execute(final CloudBlobClient client, final CloudBlob blob, final final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, accessCondition, - blobOptions, opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, leaseTimeInSeconds, + proposedLeaseId, null, accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -316,20 +341,28 @@ protected final void assertCorrectBlobType() throws StorageException { * Breaks the lease but ensures that another client cannot acquire a new lease until the current lease period has * expired. * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @return The time, in seconds, remaining in the lease period. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public final long breakLease() throws StorageException { - return this.breakLease(null, null, null); + public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException { + return this.breakLease(breakPeriodInSeconds, null, null, null); } /** * Breaks the lease, using the specified request options and operation context, but ensures that another client * cannot acquire a new lease until the current lease period has expired. * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param options @@ -347,8 +380,8 @@ public final long breakLease() throws StorageException { * If a storage service error occurred. */ @DoesServiceRequest - public final long breakLease(final AccessCondition accessCondition, BlobRequestOptions options, - OperationContext opContext) throws StorageException { + public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -368,8 +401,8 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, + breakPeriodInSeconds, accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -401,9 +434,10 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageException { + public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageException, URISyntaxException { this.copyFromBlob(sourceBlob, null, null, null, null); } @@ -428,10 +462,57 @@ public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageExcepti * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException * */ @DoesServiceRequest public final void copyFromBlob(final CloudBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + this.copyFromBlob(sourceBlob.uri, sourceAccessCondition, destinationAccessCondition, options, opContext); + + } + + /** + * Copies an existing blob's contents, properties, and metadata to this instance of the CloudBlob + * class. + * + * @param source + * A URI The URI of a source blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void copyFromBlob(final URI source) throws StorageException { + this.copyFromBlob(source, null, null, null, null); + } + + /** + * Copies an existing blob's contents, properties, and metadata to a new blob, using the specified access + * conditions, lease ID, request options, and operation context. + * + * @param source + * A URI The URI of a source 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. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final void copyFromBlob(final URI source, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -453,15 +534,94 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.copyFrom(blob.getTransformedAddress(opContext), - blobOptions.getTimeoutIntervalInMs(), sourceBlob.getCanonicalName(false), blob.snapshotID, + blobOptions.getTimeoutIntervalInMs(), source.toString(), blob.snapshotID, sourceAccessCondition, destinationAccessCondition, blobOptions, opContext); - BlobRequest.addMetadata(request, sourceBlob.metadata, opContext); + BlobRequest.addMetadata(request, blob.metadata, opContext); client.getCredentials().signRequest(request, 0); this.setResult(ExecutionEngine.processRequest(request, opContext)); - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updatePropertiesFromResponse(request); + blob.copyState = BaseResponse.getCopyState(request); + + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Aborts an ongoing blob copy operation. + * + * @param copyId + * A String object that identifying the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void abortCopy(final String copyId) throws StorageException { + this.abortCopy(copyId, null, null, null); + } + + /** + * Aborts an ongoing blob copy operation. + * + * @param copyId + * A String object that identifying the copy operation. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final void abortCopy(final String copyId, final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlob blob, final OperationContext opContext) + throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = BlobRequest.abortCopy(blob.getTransformedAddress(opContext), + blobOptions.getTimeoutIntervalInMs(), copyId, accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { this.setNonExceptionedRetryableFailure(true); return null; } @@ -996,6 +1156,7 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op blob.properties = retrievedAttributes.getProperties(); blob.metadata = retrievedAttributes.getMetadata(); + blob.copyState = retrievedAttributes.getCopyState(); return null; } @@ -1279,107 +1440,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { opContext); } - /** - * Returns a shared access signature for the blob using the specified shared access policy. Note this does not - * contain the leading "?". - * - * @param policy - * A SharedAccessPolicy object that represents the access policy for the shared access - * signature. - * @return A String that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final SharedAccessPolicy policy) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(policy, null); - } - - /** - * Returns a shared access signature for the blob using the specified shared access policy and operation context. - * Note this does not contain the leading "?". - * - * @param policy - * A SharedAccessPolicy object that represents the access policy for the shared access - * signature. - * @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 that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final SharedAccessPolicy policy, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(policy, null, opContext); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier. Note this does not - * contain the leading "?". - * - * @param groupPolicyIdentifier - * A String that represents the container-level access policy. - * - * @return A String that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final String groupPolicyIdentifier) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(groupPolicyIdentifier, null); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier and operation context. - * Note this does not contain the leading "?". - * - * @param groupPolicyIdentifier - * A String that represents the container-level access policy. - * @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 that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final String groupPolicyIdentifier, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(null, groupPolicyIdentifier, opContext); - } - /** * Returns a shared access signature for the blob using the specified group policy identifier and operation context. * Note this does not contain the leading "?". @@ -1403,12 +1463,8 @@ public final String generateSharedAccessSignature(final String groupPolicyIdenti * @throws StorageException * If a storage service error occurred. */ - private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, OperationContext opContext) throws InvalidKeyException, - StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } + public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { if (!this.blobServiceClient.getCredentials().canCredentialsSignRequest()) { throw new IllegalArgumentException( @@ -1423,7 +1479,7 @@ private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy final String resourceName = this.getCanonicalName(true); final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, - groupPolicyIdentifier, resourceName, this.blobServiceClient, opContext); + groupPolicyIdentifier, resourceName, this.blobServiceClient, null); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, groupPolicyIdentifier, "b", signature); @@ -1534,6 +1590,15 @@ public final BlobProperties getProperties() { return this.properties; } + /** + * Returns the blob's copy state. + * + * @return A {@link CopyState} object that represents the copy state of the blob. + */ + public CopyState getCopyState() { + return this.copyState; + } + /** * Returns the snapshot or shared access signature qualified URI for this blob. * @@ -1697,7 +1762,7 @@ public final BlobInputStream openInputStream(final AccessCondition accessConditi * @throws StorageException * If a storage service error occurred. * */ - private void parseURIQueryStringAndVerify(final URI completeUri, final CloudBlobClient existingClient, + protected void parseURIQueryStringAndVerify(final URI completeUri, final CloudBlobClient existingClient, final boolean usePathStyleUris) throws StorageException { Utility.assertNotNull("resourceUri", completeUri); @@ -1833,8 +1898,8 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RELEASE, accessCondition, - blobOptions, opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RELEASE, null, null, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -1913,8 +1978,96 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RENEW, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RENEW, null, null, null, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Changes an existing lease. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition) + throws StorageException { + this.changeLease(proposedLeaseId, accessCondition, null, null); + } + + /** + * Changes an existing lease using the specified proposedLeaseId, request options and operation context. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlob blob, final OperationContext opContext) + throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.CHANGE, null, proposedLeaseId, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -1964,6 +2117,16 @@ protected final void setProperties(final BlobProperties properties) { this.properties = properties; } + /** + * Reserved for internal use. + * + * @param copyState + * the copyState to set + */ + public void setCopyState(final CopyState copyState) { + this.copyState = copyState; + } + /** * Sets the blob snapshot ID. * @@ -2030,8 +2193,8 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java index dffa565ca014..ab8bd64df564 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java @@ -28,6 +28,8 @@ import javax.xml.stream.XMLStreamException; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; +import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -43,6 +45,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -617,7 +620,7 @@ public BlobContainerPermissions execute(final CloudBlobClient client, final Clou final String aclString = ContainerResponse.getAcl(request); final BlobContainerPermissions containerAcl = getContainerAcl(aclString); - final AccessPolicyResponse response = new AccessPolicyResponse(request.getInputStream()); + final BlobAccessPolicyResponse response = new BlobAccessPolicyResponse(request.getInputStream()); for (final String key : response.getAccessIdentifiers().keySet()) { containerAcl.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); @@ -707,232 +710,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { opContext); } - /** - * Returns a shared access signature for the blob using the specified shared access policy. Note this does not - * contain the leading "?". - *

- * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - *

- * The parameters of the shared access signature that govern access are: - *

    - *
  • The start time at which the signature becomes valid.
  • - *
  • The time at which it expires.
  • - *
  • The permissions that it grants.
  • - *
- * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - *
    - *
  • - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.
  • - *
  • - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the groupPolicyIdentifier parameter.
  • - *
- * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see Creating a Shared Access Signature. For - * details on container-level access policies, see Specifying a Container-Level Access Policy. - * - * @param policy - * A SharedAccessPolicy object that represents the access policy for the shared access - * signature. - * - * @return A String that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final SharedAccessPolicy policy) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(policy, null); - } - - /** - * Returns a shared access signature for the blob using the shared access policy and operation context. Note this - * does not contain the leading "?". - *

- * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - *

- * The parameters of the shared access signature that govern access are: - *

    - *
  • The start time at which the signature becomes valid.
  • - *
  • The time at which it expires.
  • - *
  • The permissions that it grants.
  • - *
- * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - *
    - *
  • - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.
  • - *
  • - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the groupPolicyIdentifier parameter.
  • - *
- * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see Creating a Shared Access Signature. For - * details on container-level access policies, see Specifying a Container-Level Access Policy. - * - * @param policy - * A SharedAccessPolicy object that represents the access policy for the shared access - * signature. - * @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 that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final SharedAccessPolicy policy, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(policy, null, opContext); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier. Note this does not - * contain the leading "?". - *

- * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - *

- * The parameters of the shared access signature that govern access are: - *

    - *
  • The start time at which the signature becomes valid.
  • - *
  • The time at which it expires.
  • - *
  • The permissions that it grants.
  • - *
- * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - *
    - *
  • - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.
  • - *
  • - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the groupPolicyIdentifier parameter.
  • - *
- * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see Creating a Shared Access Signature. For - * details on container-level access policies, see Specifying a Container-Level Access Policy. - * - * @param groupPolicyIdentifier - * A String that represents the container-level access policy. - * - * @return A String that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final String groupPolicyIdentifier) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(groupPolicyIdentifier, null); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier and operation context. - * Note this does not contain the leading "?". - *

- * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - *

- * The parameters of the shared access signature that govern access are: - *

    - *
  • The start time at which the signature becomes valid.
  • - *
  • The time at which it expires.
  • - *
  • The permissions that it grants.
  • - *
- * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - *
    - *
  • - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.
  • - *
  • - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the groupPolicyIdentifier parameter.
  • - *
- * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see Creating a Shared Access Signature. For - * details on container-level access policies, see Specifying a Container-Level Access Policy. - * - * @param groupPolicyIdentifier - * A String that represents the container-level access policy. - * @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 that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final String groupPolicyIdentifier, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(null, groupPolicyIdentifier, opContext); - } - /** * Returns a shared access signature for the container. Note this does not contain the leading "?". * @@ -945,12 +722,8 @@ public String generateSharedAccessSignature(final String groupPolicyIdentifier, * @throws StorageException * @throws IllegalArgumentException */ - private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, OperationContext opContext) throws InvalidKeyException, - StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } + public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { if (!this.blobServiceClient.getCredentials().canCredentialsSignRequest()) { final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the BlobServiceClient."; @@ -960,7 +733,7 @@ private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy final String resourceName = this.getSharedAccessCanonicalName(); final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, - groupPolicyIdentifier, resourceName, this.blobServiceClient, opContext); + groupPolicyIdentifier, resourceName, this.blobServiceClient, null); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, groupPolicyIdentifier, "c", signature); @@ -1854,4 +1627,432 @@ public Void execute(final CloudBlobClient client, final CloudBlobContainer conta ExecutionEngine .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); } + + /** + * Acquires a new lease on the container. + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @return A String that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId) + throws StorageException { + return this.acquireLease(leaseTimeInSeconds, proposedLeaseId, null, null, null); + } + + /** + * Acquires a new lease on the container using the specified visibilityTimeoutInSeconds, proposedLeaseId, request + * options and operation context. + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @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 String that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public String execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, leaseTimeInSeconds, proposedLeaseId, null, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + + return BlobResponse.getLeaseID(request, opContext); + } + }; + + return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Renews an existing lease. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the container. The LeaseID + * is required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void renewLease(final AccessCondition accessCondition) throws StorageException { + this.renewLease(accessCondition, null, null); + } + + /** + * Renews an existing lease using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void renewLease(final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.RENEW, null, null, null, accessCondition, blobOptions, + opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Releases the lease on the container. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void releaseLease(final AccessCondition accessCondition) throws StorageException { + this.releaseLease(accessCondition, null, null); + } + + /** + * Releases the lease on the container using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob.The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void releaseLease(final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.RELEASE, null, null, null, accessCondition, blobOptions, + opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Breaks the lease but ensures that another client cannot acquire a new lease until the current lease period has + * expired. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @return The time, in seconds, remaining in the lease period. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException { + return this.breakLease(breakPeriodInSeconds, null, null, null); + } + + /** + * Breaks the lease, using the specified request options and operation context, but ensures that another client + * cannot acquire a new lease until the current lease period has expired. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @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 The time, in seconds, remaining in the lease period. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Long execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, breakPeriodInSeconds, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return -1L; + } + + container.updatePropertiesFromResponse(request); + final String leaseTime = BlobResponse.getLeaseTime(request, opContext); + return Utility.isNullOrEmpty(leaseTime) ? -1L : Long.parseLong(leaseTime); + } + }; + + return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Changes an existing lease. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition) + throws StorageException { + this.changeLease(proposedLeaseId, accessCondition, null, null); + } + + /** + * Changes an existing lease using the specified proposedLeaseId, request options and operation context. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.CHANGE, null, proposedLeaseId, null, accessCondition, + blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java index d6cbf59c3ded..c36d4993bf27 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java @@ -38,6 +38,25 @@ */ public final class CloudBlockBlob extends CloudBlob { + /** + * Creates an instance of the CloudBlockBlob class using the specified relative URI and storage service + * client. + * + * @param uri + * A java.net.URI object that represents the relative URI to the blob, beginning with the + * container name. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final URI uri) throws StorageException { + super(BlobType.BLOCK_BLOB); + + Utility.assertNotNull("blobAbsoluteUri", uri); + this.uri = uri; + this.parseURIQueryStringAndVerify(uri, null, Utility.determinePathStyleFromUri(uri, null));; + } + /** * Creates an instance of the CloudBlockBlob class by copying values from another cloud block blob. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java index 9bab8cf99b0f..d55d8564b0b6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java @@ -37,6 +37,24 @@ * Represents a Windows Azure page blob. */ public final class CloudPageBlob extends CloudBlob { + /** + * Creates an instance of the CloudPageBlob class using the specified relative URI and storage service + * client. + * + * @param uri + * A java.net.URI object that represents the relative URI to the blob, beginning with the + * container name. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final URI uri) throws StorageException { + super(BlobType.PAGE_BLOB); + + Utility.assertNotNull("blobAbsoluteUri", uri); + this.uri = uri; + this.parseURIQueryStringAndVerify(uri, null, Utility.determinePathStyleFromUri(uri, null));; + } /** * Creates an instance of the CloudPageBlob class by copying values from another page blob. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java index 435421772baa..4c00cef60eff 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java @@ -27,6 +27,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.Credentials; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -34,6 +35,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseRequest; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ListingContext; /** @@ -291,6 +293,75 @@ public static HttpURLConnection setMetadata(final URI uri, final int timeout, fi return BaseRequest.setMetadata(uri, timeout, containerBuilder, opContext); } + /** + * Constructs a HttpURLConnection to Acquire,Release,Break, or Renew a blob lease. Sign with 0 length. + * + * @param uri + * The absolute URI to the blob + * @param timeout + * The server timeout interval + * @param action + * the LeaseAction to perform + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A String that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param blobOptions + * the options to use for the request. + * @param opContext + * a tracking object for the request + * @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 lease(final URI uri, final int timeout, final LeaseAction action, + final Integer leaseTimeInSeconds, final String proposedLeaseId, final Integer breakPeriodInSeconds, + final AccessCondition accessCondition, final BlobRequestOptions blobOptions, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + + final UriQueryBuilder builder = getContainerUriQueryBuilder(); + builder.add("comp", "lease"); + + final HttpURLConnection request = createURLConnection(uri, timeout, builder, opContext); + + request.setDoOutput(true); + request.setRequestMethod("PUT"); + request.setFixedLengthStreamingMode(0); + request.setRequestProperty("x-ms-lease-action", action.toString()); + + if (leaseTimeInSeconds != null) { + request.setRequestProperty("x-ms-lease-duration", leaseTimeInSeconds.toString()); + } + else { + request.setRequestProperty("x-ms-lease-duration", "-1"); + } + + if (proposedLeaseId != null) { + request.setRequestProperty("x-ms-proposed-lease-id", proposedLeaseId); + } + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + } + return request; + } + /** * Signs the request for Shared Key authentication. * @@ -330,7 +401,7 @@ public static void signRequestForSharedKeyLite(final HttpURLConnection request, * @throws XMLStreamException */ public static void writeSharedAccessIdentifiersToStream( - final HashMap sharedAccessPolicies, final StringWriter outWriter) + final HashMap sharedAccessPolicies, final StringWriter outWriter) throws XMLStreamException { Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); Utility.assertNotNull("outWriter", outWriter); @@ -338,44 +409,44 @@ public static void writeSharedAccessIdentifiersToStream( final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); - if (sharedAccessPolicies.keySet().size() > BlobConstants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { final String errorMessage = String .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container.", - sharedAccessPolicies.keySet().size(), BlobConstants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); throw new IllegalArgumentException(errorMessage); } // default is UTF8 xmlw.writeStartDocument(); - xmlw.writeStartElement(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); - for (final Entry entry : sharedAccessPolicies.entrySet()) { - final SharedAccessPolicy policy = entry.getValue(); - xmlw.writeStartElement(BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + for (final Entry entry : sharedAccessPolicies.entrySet()) { + final SharedAccessBlobPolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); // Set the identifier xmlw.writeStartElement(Constants.ID); xmlw.writeCharacters(entry.getKey()); xmlw.writeEndElement(); - xmlw.writeStartElement(BlobConstants.ACCESS_POLICY); + xmlw.writeStartElement(Constants.ACCESS_POLICY); // Set the Start Time - xmlw.writeStartElement(BlobConstants.START); + xmlw.writeStartElement(Constants.START); xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); // end Start xmlw.writeEndElement(); // Set the Expiry Time - xmlw.writeStartElement(BlobConstants.EXPIRY); + xmlw.writeStartElement(Constants.EXPIRY); xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); // end Expiry xmlw.writeEndElement(); // Set the Permissions - xmlw.writeStartElement(BlobConstants.PERMISSION); - xmlw.writeCharacters(SharedAccessPolicy.permissionsToString(policy.getPermissions())); + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessBlobPolicy.permissionsToString(policy.getPermissions())); // end Permission xmlw.writeEndElement(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java index 87e819cb9c03..f03144533da8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java @@ -68,9 +68,12 @@ public static BlobContainerAttributes getAttributes(final HttpURLConnection requ final BlobContainerProperties containerProperties = containerAttributes.getProperties(); containerProperties.setEtag(BaseResponse.getEtag(request)); containerProperties.setLastModified(new Date(request.getLastModified())); - containerAttributes.setMetadata(getMetadata(request)); + containerProperties.setLeaseStatus(BaseResponse.getLeaseStatus(request)); + containerProperties.setLeaseState(BaseResponse.getLeaseState(request)); + containerProperties.setLeaseDuration(BaseResponse.getLeaseDuration(request)); + return containerAttributes; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java new file mode 100644 index 000000000000..6c1a9e921afe --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java @@ -0,0 +1,122 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.client; + +import java.net.URI; +import java.util.Date; + +/** + * Represents the attributes of a copy operation. + * + */ +public final class CopyState { + /** + * Holds the Name of the Container + */ + private String copyId; + + /** + * Holds the time the copy operation completed, whether completion was due to a successful copy, abortion, or a + * failure. + */ + private Date completionTime; + + /** + * Holds the status of the copy operation. + */ + private CopyStatus status; + + /** + * Holds the source URI of a copy operation. + */ + private URI source; + + /** + * Holds the number of bytes copied in the operation so far. + */ + private Long bytesCopied; + + /** + * Holds the total number of bytes in the source of the copy. + */ + private Long totalBytes; + + /** + * Holds the description of the current status. + */ + private String statusDescription; + + /** + * Initializes a new instance of the CopyState class + */ + public CopyState() { + } + + public String getCopyId() { + return this.copyId; + } + + public Date getCompletionTime() { + return this.completionTime; + } + + public CopyStatus getStatus() { + return this.status; + } + + public URI getSource() { + return this.source; + } + + public Long getBytesCopied() { + return this.bytesCopied; + } + + public Long getTotalBytes() { + return this.totalBytes; + } + + public String getStatusDescription() { + return this.statusDescription; + } + + public void setCopyId(final String copyId) { + this.copyId = copyId; + } + + public void setCompletionTime(final Date completionTime) { + this.completionTime = completionTime; + } + + public void setStatus(final CopyStatus status) { + this.status = status; + } + + public void setSource(final URI source) { + this.source = source; + } + + public void setBytesCopied(final Long bytesCopied) { + this.bytesCopied = bytesCopied; + } + + public void setTotalBytes(final Long totalBytes) { + this.totalBytes = totalBytes; + } + + public void setStatusDescription(final String statusDescription) { + this.statusDescription = statusDescription; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java new file mode 100644 index 000000000000..9e4d13c91dab --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java @@ -0,0 +1,86 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.client; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * Represents the status of a copy blob operation. + */ +public enum CopyStatus { + /** + * The copy status is not specified.. + */ + UNSPECIFIED, + + /** + * The copy status is invalid. + */ + INVALID, + + /** + * The copy operation is pending. + */ + PENDING, + + /** + * The copy operation succeeded. + */ + SUCCESS, + + /** + * The copy operation has been aborted. + */ + ABORTED, + + /** + * The copy operation encountered an error. + */ + FAILED; + + /** + * Parses a copy status from the given string. + * + * @param typeString + * A String that represents the string to parse. + * + * @return A CopyStatus value that represents the copy status. + */ + public static CopyStatus parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("invalid".equals(typeString.toLowerCase(Locale.US))) { + return INVALID; + } + else if ("pending".equals(typeString.toLowerCase(Locale.US))) { + return PENDING; + } + else if ("success".equals(typeString.toLowerCase(Locale.US))) { + return SUCCESS; + } + else if ("aborted".equals(typeString.toLowerCase(Locale.US))) { + return ABORTED; + } + else if ("failed".equals(typeString.toLowerCase(Locale.US))) { + return FAILED; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java similarity index 87% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java index 52866696d5b6..c8dc1c17dff8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java @@ -19,7 +19,7 @@ /** * Specifies the set of possible permissions for a shared access policy. */ -public enum SharedAccessPermissions { +public enum SharedAccessBlobPermissions { /** * Specifies Read access granted. */ @@ -48,8 +48,8 @@ public enum SharedAccessPermissions { * @return A java.util.EnumSet object that contains the SharedAccessPermissions values * corresponding to the specified byte value. */ - protected static EnumSet fromByte(final byte value) { - final EnumSet retSet = EnumSet.noneOf(SharedAccessPermissions.class); + protected static EnumSet fromByte(final byte value) { + final EnumSet retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); if (value == READ.value) { retSet.add(READ); @@ -79,7 +79,7 @@ protected static EnumSet fromByte(final byte value) { * @param val * The value being assigned. */ - SharedAccessPermissions(final byte val) { + SharedAccessBlobPermissions(final byte val) { this.value = val; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java similarity index 77% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java index 3b04f396ffa6..937b558ab4c6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java @@ -23,7 +23,7 @@ * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access * signature. */ -public final class SharedAccessPolicy { +public final class SharedAccessBlobPolicy { /** * Assigns shared access permissions using the specified permissions string. @@ -39,26 +39,26 @@ public final class SharedAccessPolicy { *
  • w: Write access.
  • * * - * @return A java.util.EnumSet object that contains {@link SharedAccessPermissions} values that + * @return A java.util.EnumSet object that contains {@link SharedAccessBlobPermissions} values that * represents the set of shared access permissions. */ - public static EnumSet permissionsFromString(final String value) { + public static EnumSet permissionsFromString(final String value) { final char[] chars = value.toCharArray(); - final EnumSet retSet = EnumSet.noneOf(SharedAccessPermissions.class); + final EnumSet retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); for (final char c : chars) { switch (c) { case 'r': - retSet.add(SharedAccessPermissions.READ); + retSet.add(SharedAccessBlobPermissions.READ); break; case 'w': - retSet.add(SharedAccessPermissions.WRITE); + retSet.add(SharedAccessBlobPermissions.WRITE); break; case 'd': - retSet.add(SharedAccessPermissions.DELETE); + retSet.add(SharedAccessBlobPermissions.DELETE); break; case 'l': - retSet.add(SharedAccessPermissions.LIST); + retSet.add(SharedAccessBlobPermissions.LIST); break; default: throw new IllegalArgumentException("value"); @@ -72,12 +72,12 @@ public static EnumSet permissionsFromString(final Strin * Converts the permissions specified for the shared access policy to a string. * * @param permissions - * A {@link SharedAccessPermissions} object that represents the shared access permissions. + * A {@link SharedAccessBlobPermissions} object that represents the shared access permissions. * * @return A String that represents the shared access permissions in the "rwdl" format, which is - * described at {@link SharedAccessPolicy#permissionsFromString}. + * described at {@link SharedAccessBlobPolicy#permissionsFromString}. */ - public static String permissionsToString(final EnumSet permissions) { + public static String permissionsToString(final EnumSet permissions) { if (permissions == null) { return Constants.EMPTY_STRING; } @@ -85,19 +85,19 @@ public static String permissionsToString(final EnumSet // The service supports a fixed order => rwdl final StringBuilder builder = new StringBuilder(); - if (permissions.contains(SharedAccessPermissions.READ)) { + if (permissions.contains(SharedAccessBlobPermissions.READ)) { builder.append("r"); } - if (permissions.contains(SharedAccessPermissions.WRITE)) { + if (permissions.contains(SharedAccessBlobPermissions.WRITE)) { builder.append("w"); } - if (permissions.contains(SharedAccessPermissions.DELETE)) { + if (permissions.contains(SharedAccessBlobPermissions.DELETE)) { builder.append("d"); } - if (permissions.contains(SharedAccessPermissions.LIST)) { + if (permissions.contains(SharedAccessBlobPermissions.LIST)) { builder.append("l"); } @@ -107,7 +107,7 @@ public static String permissionsToString(final EnumSet /** * The permissions for a shared access signature associated with this shared access policy. */ - private EnumSet permissions; + private EnumSet permissions; /** * The expiry time for a shared access signature associated with this shared access policy. @@ -122,14 +122,14 @@ public static String permissionsToString(final EnumSet /** * Creates an instance of the SharedAccessPolicy class. * */ - public SharedAccessPolicy() { + public SharedAccessBlobPolicy() { // Empty Default Ctor } /** * @return the permissions */ - public EnumSet getPermissions() { + public EnumSet getPermissions() { return this.permissions; } @@ -151,7 +151,7 @@ public Date getSharedAccessStartTime() { * @param permissions * the permissions to set */ - public void setPermissions(final EnumSet permissions) { + public void setPermissions(final EnumSet permissions) { this.permissions = permissions; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java deleted file mode 100644 index 98001ef8d378..000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Copyright 2011 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.windowsazure.services.blob.client; - -import java.security.InvalidKeyException; -import java.util.HashMap; -import java.util.Map.Entry; - -import com.microsoft.windowsazure.services.core.storage.Constants; -import com.microsoft.windowsazure.services.core.storage.OperationContext; -import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; -import com.microsoft.windowsazure.services.core.storage.StorageException; -import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; -import com.microsoft.windowsazure.services.core.storage.utils.Utility; - -/** - * RESERVED FOR INTERNAL USE. Contains helper methods for implementing shared access signatures. - */ -final class SharedAccessSignatureHelper { - /** - * Get the complete query builder for creating the Shared Access Signature query. - * - * @param policy - * The shared access policy to hash. - * @param groupPolicyIdentifier - * An optional identifier for the policy. - * @param resourceType - * Either "b" for blobs or "c" for containers. - * @param signature - * The signature to use. - * @return The finished query builder - * @throws IllegalArgumentException - * @throws StorageException - */ - protected static UriQueryBuilder generateSharedAccessSignature(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, final String resourceType, final String signature) - throws StorageException { - Utility.assertNotNullOrEmpty("resourceType", resourceType); - Utility.assertNotNull("signature", signature); - - final UriQueryBuilder builder = new UriQueryBuilder(); - if (policy != null) { - String permissions = SharedAccessPolicy.permissionsToString(policy.getPermissions()); - - if (Utility.isNullOrEmpty(permissions)) { - permissions = null; - } - - final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); - if (!Utility.isNullOrEmpty(startString)) { - builder.add(BlobConstants.QueryConstants.SIGNED_START, startString); - } - - final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); - if (!Utility.isNullOrEmpty(stopString)) { - builder.add(BlobConstants.QueryConstants.SIGNED_EXPIRY, stopString); - } - - if (!Utility.isNullOrEmpty(permissions)) { - builder.add(BlobConstants.QueryConstants.SIGNED_PERMISSIONS, permissions); - } - } - - builder.add(BlobConstants.QueryConstants.SIGNED_RESOURCE, resourceType); - - if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { - builder.add(BlobConstants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); - } - - if (!Utility.isNullOrEmpty(signature)) { - builder.add(BlobConstants.QueryConstants.SIGNATURE, signature); - } - - return builder; - } - - /** - * Get the signature hash embedded inside the Shared Access Signature. - * - * @param policy - * The shared access policy to hash. - * @param groupPolicyIdentifier - * An optional identifier for the policy. - * @param resourceName - * the resource name. - * @param client - * the CloudBlobClient associated with the object. - * @param opContext - * an object used to track the execution of the operation - * @return the signature hash embedded inside the Shared Access Signature. - * @throws InvalidKeyException - * @throws StorageException - */ - protected static String generateSharedAccessSignatureHash(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, final String resourceName, final CloudBlobClient client, - final OperationContext opContext) throws InvalidKeyException, StorageException { - Utility.assertNotNullOrEmpty("resourceName", resourceName); - Utility.assertNotNull("client", client); - - String stringToSign = null; - - if (policy == null) { - // Revokable access - Utility.assertNotNullOrEmpty("groupPolicyIdentifier", groupPolicyIdentifier); - stringToSign = String.format("%s\n%s\n%s\n%s\n%s", Constants.EMPTY_STRING, Constants.EMPTY_STRING, - Constants.EMPTY_STRING, resourceName, groupPolicyIdentifier); - } - else { - // Non Revokable access - if (policy.getSharedAccessExpiryTime() == null) { - throw new IllegalArgumentException("Policy Expiry time is mandatory and cannot be null"); - } - - if (policy.getPermissions() == null) { - throw new IllegalArgumentException("Policy permissions are mandatory and cannot be null"); - } - - stringToSign = String.format("%s\n%s\n%s\n%s\n%s", - SharedAccessPolicy.permissionsToString(policy.getPermissions()), - Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()), - Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()), resourceName, - groupPolicyIdentifier == null ? Constants.EMPTY_STRING : groupPolicyIdentifier); - } - - stringToSign = Utility.safeDecode(stringToSign); - final String signature = client.getCredentials().computeHmac256(stringToSign, opContext); - - // add logging - return signature; - } - - /** - * Parses the query parameters and populates a StorageCredentialsSharedAccessSignature object if one is present. - * - * @param queryParams - * the parameters to parse - * @return the StorageCredentialsSharedAccessSignature if one is present, otherwise null - * @throws IllegalArgumentException - * @throws StorageException - * an exception representing any error which occurred during the operation. - */ - protected static StorageCredentialsSharedAccessSignature parseQuery(final HashMap queryParams) - throws StorageException { - String signature = null; - String signedStart = null; - String signedExpiry = null; - String signedResource = null; - String sigendPermissions = null; - String signedIdentifier = null; - String signedVersion = null; - - boolean sasParameterFound = false; - - StorageCredentialsSharedAccessSignature credentials = null; - - for (final Entry entry : queryParams.entrySet()) { - final String lowerKey = entry.getKey().toLowerCase(Utility.LOCALE_US); - - if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_START)) { - signedStart = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_EXPIRY)) { - signedExpiry = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_PERMISSIONS)) { - sigendPermissions = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_RESOURCE)) { - signedResource = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_IDENTIFIER)) { - signedIdentifier = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNATURE)) { - signature = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_VERSION)) { - signedVersion = entry.getValue()[0]; - sasParameterFound = true; - } - } - - if (sasParameterFound) { - if (signature == null || signedResource == null) { - final String errorMessage = "Missing mandatory parameters for valid Shared Access Signature"; - throw new IllegalArgumentException(errorMessage); - } - - final UriQueryBuilder builder = new UriQueryBuilder(); - - if (!Utility.isNullOrEmpty(signedStart)) { - builder.add(BlobConstants.QueryConstants.SIGNED_START, signedStart); - } - - if (!Utility.isNullOrEmpty(signedExpiry)) { - builder.add(BlobConstants.QueryConstants.SIGNED_EXPIRY, signedExpiry); - } - - if (!Utility.isNullOrEmpty(sigendPermissions)) { - builder.add(BlobConstants.QueryConstants.SIGNED_PERMISSIONS, sigendPermissions); - } - - builder.add(BlobConstants.QueryConstants.SIGNED_RESOURCE, signedResource); - - if (!Utility.isNullOrEmpty(signedIdentifier)) { - builder.add(BlobConstants.QueryConstants.SIGNED_IDENTIFIER, signedIdentifier); - } - - if (!Utility.isNullOrEmpty(signedVersion)) { - builder.add(BlobConstants.QueryConstants.SIGNED_VERSION, signedVersion); - } - - if (!Utility.isNullOrEmpty(signature)) { - builder.add(BlobConstants.QueryConstants.SIGNATURE, signature); - } - - final String token = builder.toString(); - credentials = new StorageCredentialsSharedAccessSignature(token); - } - - return credentials; - } - - /** - * Private Default Ctor. - */ - private SharedAccessSignatureHelper() { - // No op - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java new file mode 100644 index 000000000000..26cefa304d70 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java @@ -0,0 +1,497 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.core.storage; + +import java.security.InvalidKeyException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; + +import com.microsoft.windowsazure.services.blob.client.SharedAccessBlobPolicy; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ServiceClient; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.queue.client.SharedAccessQueuePolicy; +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePolicy; + +/** + * RESERVED FOR INTERNAL USE. Contains helper methods for implementing shared access signatures. + */ +public class SharedAccessSignatureHelper { + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceType + * Either "b" for blobs or "c" for containers. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessBlobPolicy policy, + final String groupPolicyIdentifier, final String resourceType, final String signature) + throws StorageException { + Utility.assertNotNullOrEmpty("resourceType", resourceType); + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (policy != null) { + String permissions = SharedAccessBlobPolicy.permissionsToString(policy.getPermissions()); + + if (Utility.isNullOrEmpty(permissions)) { + permissions = null; + } + + final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + } + + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, resourceType); + + if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessQueuePolicy policy, + final String groupPolicyIdentifier, final String signature) throws StorageException { + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (policy != null) { + String permissions = SharedAccessQueuePolicy.permissionsToString(policy.getPermissions()); + + if (Utility.isNullOrEmpty(permissions)) { + permissions = null; + } + + final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + } + + if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final String permissions, final Date startTime, + final Date expiryTime, final String startPatitionKey, final String startRowKey, + final String endPatitionKey, final String endRowKey, final String accessPolicyIdentifier, + final String resourceType, final String tableName, final String signature, final String accountKeyName) + throws StorageException { + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + + final String startString = Utility.getUTCTimeOrEmpty(startTime); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(expiryTime); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(startPatitionKey)) { + builder.add(Constants.QueryConstants.START_PARTITION_KEY, startPatitionKey); + } + + if (!Utility.isNullOrEmpty(startRowKey)) { + builder.add(Constants.QueryConstants.START_ROW_KEY, startRowKey); + } + + if (!Utility.isNullOrEmpty(endPatitionKey)) { + builder.add(Constants.QueryConstants.END_PARTITION_KEY, endPatitionKey); + } + + if (!Utility.isNullOrEmpty(endRowKey)) { + builder.add(Constants.QueryConstants.END_ROW_KEY, endRowKey); + } + + if (!Utility.isNullOrEmpty(accessPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, accessPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(resourceType)) { + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, resourceType); + } + + if (!Utility.isNullOrEmpty(tableName)) { + builder.add(Constants.QueryConstants.SAS_TABLE_NAME, tableName); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + if (!Utility.isNullOrEmpty(accountKeyName)) { + builder.add(Constants.QueryConstants.SIGNED_KEY, accountKeyName); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessTablePolicy policy, + final String startPartitionKey, final String startRowKey, final String endPartitionKey, + final String endRowKey, final String accessPolicyIdentifier, final String tableName, + final String signature, final String accountKeyName) throws StorageException { + + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessTablePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignature(permissionString, startTime, expiryTime, startPartitionKey, startRowKey, + endPartitionKey, endRowKey, accessPolicyIdentifier, null, tableName, signature, accountKeyName); + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for blob service. + * + * @param policy + * The shared access policy to hash. + * @param accessPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final SharedAccessBlobPolicy policy, + final String accessPolicyIdentifier, final String resourceName, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessBlobPolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, false, null, null, null, null, client, opContext); + + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for queue service. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + + public static String generateSharedAccessSignatureHash(final SharedAccessQueuePolicy policy, + final String accessPolicyIdentifier, final String resourceName, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessQueuePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, false, null, null, null, null, client, opContext); + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for all storage service. + * + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final String permissions, final Date startTime, + final Date expiryTime, final String resourceName, final String accessPolicyIdentifier, + final boolean useTableSas, final String startPatitionKey, final String startRowKey, + final String endPatitionKey, final String endRowKey, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + Utility.assertNotNullOrEmpty("resourceName", resourceName); + Utility.assertNotNull("client", client); + + String stringToSign = String.format("%s\n%s\n%s\n%s\n%s\n%s", permissions == null ? Constants.EMPTY_STRING + : permissions, Utility.getUTCTimeOrEmpty(startTime), Utility.getUTCTimeOrEmpty(expiryTime), + resourceName, accessPolicyIdentifier == null ? Constants.EMPTY_STRING : accessPolicyIdentifier, + Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (useTableSas) { + stringToSign = String.format("%s\n%s\n%s\n%s\n%s", stringToSign, + startPatitionKey == null ? Constants.EMPTY_STRING : startPatitionKey, + startRowKey == null ? Constants.EMPTY_STRING : startRowKey, + endPatitionKey == null ? Constants.EMPTY_STRING : endPatitionKey, + endRowKey == null ? Constants.EMPTY_STRING : endRowKey); + } + + stringToSign = Utility.safeDecode(stringToSign); + final String signature = client.getCredentials().computeHmac256(stringToSign, opContext); + + // add logging + return signature; + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for blob service. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final SharedAccessTablePolicy policy, + final String accessPolicyIdentifier, final String resourceName, final String startPartitionKey, + final String startRowKey, final String endPartitionKey, final String endRowKey, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessTablePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, true, startPartitionKey, startRowKey, endPartitionKey, endRowKey, client, + opContext); + + } + + /** + * Parses the query parameters and populates a StorageCredentialsSharedAccessSignature object if one is present. + * + * @param queryParams + * the parameters to parse + * @return the StorageCredentialsSharedAccessSignature if one is present, otherwise null + * @throws IllegalArgumentException + * @throws StorageException + * an exception representing any error which occurred during the operation. + */ + public static StorageCredentialsSharedAccessSignature parseQuery(final HashMap queryParams) + throws StorageException { + String signature = null; + String signedStart = null; + String signedExpiry = null; + String signedResource = null; + String sigendPermissions = null; + String signedIdentifier = null; + String signedVersion = null; + + boolean sasParameterFound = false; + + StorageCredentialsSharedAccessSignature credentials = null; + + for (final Entry entry : queryParams.entrySet()) { + final String lowerKey = entry.getKey().toLowerCase(Utility.LOCALE_US); + + if (lowerKey.equals(Constants.QueryConstants.SIGNED_START)) { + signedStart = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_EXPIRY)) { + signedExpiry = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_PERMISSIONS)) { + sigendPermissions = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_RESOURCE)) { + signedResource = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_IDENTIFIER)) { + signedIdentifier = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNATURE)) { + signature = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_VERSION)) { + signedVersion = entry.getValue()[0]; + sasParameterFound = true; + } + } + + if (sasParameterFound) { + if (signature == null) { + final String errorMessage = "Missing mandatory parameters for valid Shared Access Signature"; + throw new IllegalArgumentException(errorMessage); + } + + final UriQueryBuilder builder = new UriQueryBuilder(); + + if (!Utility.isNullOrEmpty(signedStart)) { + builder.add(Constants.QueryConstants.SIGNED_START, signedStart); + } + + if (!Utility.isNullOrEmpty(signedExpiry)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, signedExpiry); + } + + if (!Utility.isNullOrEmpty(sigendPermissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, sigendPermissions); + } + + if (!Utility.isNullOrEmpty(signedResource)) { + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, signedResource); + } + + if (!Utility.isNullOrEmpty(signedIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, signedIdentifier); + } + + if (!Utility.isNullOrEmpty(signedVersion)) { + builder.add(Constants.QueryConstants.SIGNED_VERSION, signedVersion); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + final String token = builder.toString(); + credentials = new StorageCredentialsSharedAccessSignature(token); + } + + return credentials; + } + + /** + * Private Default Ctor. + */ + private SharedAccessSignatureHelper() { + // No op + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java similarity index 64% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java index d651b14ac6c7..9e513711cdb3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.microsoft.windowsazure.services.blob.client; +package com.microsoft.windowsazure.services.core.storage; import java.io.InputStream; import java.text.ParseException; @@ -22,13 +22,12 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.utils.Utility; /** * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. */ -final class AccessPolicyResponse { +public abstract class AccessPolicyResponseBase { /** * Holds a flag indicating if the response has been parsed or not. */ @@ -37,7 +36,7 @@ final class AccessPolicyResponse { /** * Holds the Hashmap of policies parsed from the stream */ - private final HashMap policies = new HashMap(); + private final HashMap policies = new HashMap(); /** * Holds a reference to the input stream to read from. @@ -50,7 +49,7 @@ final class AccessPolicyResponse { * @param stream * the input stream to read error details from. */ - public AccessPolicyResponse(final InputStream stream) { + public AccessPolicyResponseBase(final InputStream stream) { this.streamRef = stream; } @@ -63,7 +62,7 @@ public AccessPolicyResponse(final InputStream stream) { * @throws ParseException * if a date is incorrectly encoded in the stream */ - public HashMap getAccessIdentifiers() throws XMLStreamException, ParseException { + public HashMap getAccessIdentifiers() throws XMLStreamException, ParseException { if (!this.isParsed) { this.parseResponse(); } @@ -93,12 +92,11 @@ public void parseResponse() throws XMLStreamException, ParseException { if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { final String name = xmlr.getName().toString(); - if (eventType == XMLStreamConstants.START_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { this.readPolicies(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { break; } } @@ -123,7 +121,7 @@ else if (eventType == XMLStreamConstants.END_DOCUMENT) { private void readPolicies(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { int eventType = xmlr.getEventType(); - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.SIGNED_IDENTIFIERS_ELEMENT); while (xmlr.hasNext()) { eventType = xmlr.next(); @@ -131,62 +129,17 @@ private void readPolicies(final XMLStreamReader xmlr) throws XMLStreamException, if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { final String name = xmlr.getName().toString(); - if (eventType == XMLStreamConstants.START_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIER_ELEMENT)) { + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.SIGNED_IDENTIFIER_ELEMENT)) { this.readSignedIdentifier(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { break; } } } - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); - } - - /** - * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. - * - * @param xmlr - * the XMLStreamReader object - * @throws XMLStreamException - * if there is a parsing exception - * @throws ParseException - * if a date value is not correctly encoded - */ - private SharedAccessPolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { - int eventType = xmlr.getEventType(); - - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.ACCESS_POLICY); - final SharedAccessPolicy retPolicy = new SharedAccessPolicy(); - - while (xmlr.hasNext()) { - eventType = xmlr.next(); - - if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { - final String name = xmlr.getName().toString(); - - if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.PERMISSION)) { - retPolicy.setPermissions(SharedAccessPolicy.permissionsFromString(Utility.readElementFromXMLReader( - xmlr, BlobConstants.PERMISSION))); - } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.START)) { - final String tempString = Utility.readElementFromXMLReader(xmlr, BlobConstants.START); - retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); - } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.EXPIRY)) { - final String tempString = Utility.readElementFromXMLReader(xmlr, BlobConstants.EXPIRY); - retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); - } - else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstants.ACCESS_POLICY)) { - break; - } - } - } - - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.ACCESS_POLICY); - return retPolicy; + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.SIGNED_IDENTIFIERS_ELEMENT); } /** @@ -201,10 +154,10 @@ else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstant */ private void readSignedIdentifier(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { int eventType = xmlr.getEventType(); - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.SIGNED_IDENTIFIER_ELEMENT); String id = null; - SharedAccessPolicy policy = null; + T policy = null; while (xmlr.hasNext()) { eventType = xmlr.next(); @@ -214,17 +167,29 @@ private void readSignedIdentifier(final XMLStreamReader xmlr) throws XMLStreamEx if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.ID)) { id = Utility.readElementFromXMLReader(xmlr, Constants.ID); } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.ACCESS_POLICY)) { + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { policy = this.readPolicyFromXML(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIER_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIER_ELEMENT)) { this.policies.put(id, policy); break; } } } - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.SIGNED_IDENTIFIER_ELEMENT); } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + protected abstract T readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, ParseException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java index 7676efe058da..ae1b15bf0b3d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java @@ -202,6 +202,46 @@ public static class HeaderConstants { */ public static final String LEASE_STATUS = PREFIX_FOR_STORAGE_HEADER + "lease-status"; + /** + * The header that specifies lease state. + */ + public static final String LEASE_STATE = PREFIX_FOR_STORAGE_HEADER + "lease-state"; + + /** + * The header that specifies lease duration. + */ + public static final String LEASE_DURATION = PREFIX_FOR_STORAGE_HEADER + "lease-duration"; + + /** + * The header that specifies copy status. + */ + public static final String COPY_STATUS = PREFIX_FOR_STORAGE_HEADER + "copy-status"; + + /** + * The header that specifies copy progress. + */ + public static final String COPY_PROGRESS = PREFIX_FOR_STORAGE_HEADER + "copy-progress"; + + /** + * The header that specifies copy status description. + */ + public static final String COPY_STATUS_DESCRIPTION = PREFIX_FOR_STORAGE_HEADER + "copy-status-description"; + + /** + * The header that specifies copy id. + */ + public static final String COPY_ID = PREFIX_FOR_STORAGE_HEADER + "copy-id"; + + /** + * The header that specifies copy source. + */ + public static final String COPY_SOURCE = PREFIX_FOR_STORAGE_HEADER + "copy-source"; + + /** + * The header that specifies copy completion time. + */ + public static final String COPY_COMPLETION_TIME = PREFIX_FOR_STORAGE_HEADER + "copy-completion-time"; + /** * The header prefix for metadata. */ @@ -272,7 +312,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2011-08-18"; + public static final String TARGET_STORAGE_VERSION = "2012-02-12"; /** * The UserAgent header. @@ -290,6 +330,81 @@ public static class HeaderConstants { public static final String USER_AGENT_VERSION = "Client v0.1.2"; } + /** + * Defines constants for use with query strings. + */ + public static class QueryConstants { + /** + * The query component for the SAS signature. + */ + public static final String SIGNATURE = "sig"; + + /** + * The query component for the signed SAS expiry time. + */ + public static final String SIGNED_EXPIRY = "se"; + + /** + * The query component for the signed SAS identifier. + */ + public static final String SIGNED_IDENTIFIER = "si"; + + /** + * The query component for the signed SAS permissions. + */ + public static final String SIGNED_PERMISSIONS = "sp"; + + /** + * The query component for the signed SAS resource. + */ + public static final String SIGNED_RESOURCE = "sr"; + + /** + * The query component for the signed SAS start time. + */ + public static final String SIGNED_START = "st"; + + /** + * The query component for the SAS start partition key. + */ + public static final String START_PARTITION_KEY = "spk"; + + /** + * The query component for the SAS start row key. + */ + public static final String START_ROW_KEY = "srk"; + + /** + * The query component for the SAS end partition key. + */ + public static final String END_PARTITION_KEY = "epk"; + + /** + * The query component for the SAS end row key. + */ + public static final String END_ROW_KEY = "erk"; + + /** + * The query component for the SAS table name. + */ + public static final String SAS_TABLE_NAME = "tn"; + + /** + * The query component for the signing SAS key. + */ + public static final String SIGNED_KEY = "sk"; + + /** + * The query component for the signed SAS version. + */ + public static final String SIGNED_VERSION = "sv"; + + /** + * The query component for snapshot time. + */ + public static final String SNAPSHOT = "snapshot"; + } + /** * The master Windows Azure Storage header prefix. */ @@ -405,6 +520,46 @@ public static class HeaderConstants { */ public static final String LEASE_STATUS_ELEMENT = "LeaseStatus"; + /** + * XML element for the lease state. + */ + public static final String LEASE_STATE_ELEMENT = "LeaseState"; + + /** + * XML element for the lease duration. + */ + public static final String LEASE_DURATION_ELEMENT = "LeaseDuration"; + + /** + * XML element for the copy id. + */ + public static final String COPY_ID_ELEMENT = "CopyId"; + + /** + * XML element for the copy status. + */ + public static final String COPY_STATUS_ELEMENT = "CopyStatus"; + + /** + * XML element for the copy source . + */ + public static final String COPY_SOURCE_ELEMENT = "CopySource"; + + /** + * XML element for the copy progress. + */ + public static final String COPY_PROGRESS_ELEMENT = "CopyProgress"; + + /** + * XML element for the copy completion time. + */ + public static final String COPY_COMPLETION_TIME_ELEMENT = "CopyCompletionTime"; + + /** + * XML element for the copy status description. + */ + public static final String COPY_STATUS_DESCRIPTION_ELEMENT = "CopyStatusDescription"; + /** * Constant signaling the resource is locked. */ @@ -466,6 +621,41 @@ public static class HeaderConstants { */ public static final String URL_ELEMENT = "Url"; + /** + * XML element for a signed identifier. + */ + public static final String SIGNED_IDENTIFIER_ELEMENT = "SignedIdentifier"; + + /** + * XML element for signed identifiers. + */ + public static final String SIGNED_IDENTIFIERS_ELEMENT = "SignedIdentifiers"; + + /** + * XML element for an access policy. + */ + public static final String ACCESS_POLICY = "AccessPolicy"; + + /** + * Maximum number of shared access policy identifiers supported by server. + */ + public static final int MAX_SHARED_ACCESS_POLICY_IDENTIFIERS = 5; + + /** + * XML element for the start time of an access policy. + */ + public static final String START = "Start"; + + /** + * XML element for the end time of an access policy. + */ + public static final String EXPIRY = "Expiry"; + + /** + * XML element for the permission of an access policy. + */ + public static final String PERMISSION = "Permission"; + /** * Private Default Ctor */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java index e506895cb851..705075775610 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java @@ -33,6 +33,16 @@ public final class Credentials { */ private final StorageKey key; + /** + * Stores the name of the access key to be used when signing the request. + */ + private String keyName; + + /** + * Stores the account name whose key is used to sign requests. + */ + private String signingAccountName; + /** * Creates an instance of the Credentials class, using the specified storage account name and access * key; the specified access key is in the form of a byte array. @@ -54,6 +64,7 @@ public Credentials(final String accountName, final byte[] key) { this.accountName = accountName; this.key = new StorageKey(key); + this.signingAccountName = accountName; } /** @@ -97,6 +108,22 @@ public String getAccountName() { return this.accountName; } + /** + * Returns the account name whose key is used to sign requests. + * Internal use only. + */ + public String getSigningAccountName() { + return this.signingAccountName; + } + + /** + * Returns the name of the access key to be used when signing the request. + * Internal use only. + */ + public String getKeyName() { + return this.keyName; + } + /** * Returns the access key to be used in signing the request. * @@ -115,4 +142,24 @@ public StorageKey getKey() { protected void setAccountName(final String accountName) { this.accountName = accountName; } + + /** + * Sets the account name whose key is used to sign requests. + * + * @param signingAccountName + * A String that represents the account name whose key is used to sign requests. + */ + protected void setSigningAccountName(final String signingAccountName) { + this.signingAccountName = signingAccountName; + } + + /** + * Sets the name of the access key to be used when signing the request. + * + * @param keyName + * A String that represents the name of the access key to be used when signing the request. + */ + protected void setKeyName(final String keyName) { + this.keyName = keyName; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java new file mode 100644 index 000000000000..2c807c8dd57f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java @@ -0,0 +1,62 @@ +/** + * Copyright 2011 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.windowsazure.services.core.storage; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * The lease duration of a resource. + */ +public enum LeaseDuration { + /** + * The lease duration is not specified. + */ + UNSPECIFIED, + + /** + * The lease duration is finite. + */ + FIXED, + + /** + * The lease duration is infinite. + */ + INFINITE; + + /** + * Parses a lease duration from the given string. + * + * @param typeString + * A String that represents the string to parse. + * + * @return A LeaseStatus value that represents the lease status. + */ + public static LeaseDuration parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("fixed".equals(typeString.toLowerCase(Locale.US))) { + return FIXED; + } + else if ("infinite".equals(typeString.toLowerCase(Locale.US))) { + return INFINITE; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java new file mode 100644 index 000000000000..c2c7f9a79e3f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java @@ -0,0 +1,86 @@ +/** + * Copyright 2011 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.windowsazure.services.core.storage; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * he lease state of a resource. + */ +public enum LeaseState { + /** + * The lease state is not specified. + */ + UNSPECIFIED, + + /** + * The lease is in the Available state. + */ + AVAILABLE, + + /** + * The lease is in the Leased state. + */ + LEASED, + + /** + * The lease is in the Expired state. + */ + EXPIRED, + + /** + * The lease is in the Breaking state. + */ + BREAKING, + + /** + * The lease is in the Broken state. + */ + BROKEN; + + /** + * Parses a lease status from the given string. + * + * @param typeString + * A String that represents the string to parse. + * + * @return A LeaseStatus value that represents the lease status. + */ + public static LeaseState parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("available".equals(typeString.toLowerCase(Locale.US))) { + return AVAILABLE; + } + else if ("locked".equals(typeString.toLowerCase(Locale.US))) { + return LEASED; + } + else if ("expired".equals(typeString.toLowerCase(Locale.US))) { + return EXPIRED; + } + else if ("breaking".equals(typeString.toLowerCase(Locale.US))) { + return BREAKING; + } + else if ("broken".equals(typeString.toLowerCase(Locale.US))) { + return BROKEN; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java index 7a1856142f5e..b82ab088f0bc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java @@ -26,19 +26,19 @@ */ public enum LeaseStatus { /** - * Specifies the blob is locked for exclusive-write access. + * Specifies the lease status is not specified. */ - LOCKED, + UNSPECIFIED, /** - * Specifies the blob is available to be locked for exclusive-write access. + * Specifies the blob is locked for exclusive-write access. */ - UNLOCKED, + LOCKED, /** - * Specifies the lease status is not specified. + * Specifies the blob is available to be locked for exclusive-write access. */ - UNSPECIFIED; + UNLOCKED; /** * Parses a lease status from the given string. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java index fba8df30bfa2..3406fc76c38b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java @@ -195,6 +195,22 @@ public String getAccountName() { return this.credentials.getAccountName(); } + /** + * Internal usage. + * Gets the name of the key used by these credentials. + */ + public String getAccountKeyName() { + return this.credentials.getKeyName(); + } + + /** + * Internal usage. + * Sets the account name that owns the key to use when signing requests. + */ + public void setSigningAccountName(final String signingAccountName) { + this.credentials.setSigningAccountName(signingAccountName); + } + /** * Returns the Base64-encoded key for the credentials. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java index b310204bc8ea..2482119f82bf 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java @@ -117,7 +117,22 @@ public enum StorageErrorCode { /** * A transport error occurred (server-side error). */ - TRANSPORT_ERROR(5); + TRANSPORT_ERROR(5), + + /** + * A lease is required to perform the operation. + */ + LEASE_ID_MISSING(21), + + /** + * The given lease ID does not match the current lease. + */ + LEASE_ID_MISMATCH(22), + + /** + * A lease ID was used when no lease currently is held. + */ + LEASE_NOT_PRESENT(23); /** * Returns the value of this enum. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java index c9fdf722b6c4..7783f7e29548 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java @@ -196,12 +196,27 @@ public static String getCanonicalPathFromCredentials(final StorageCredentials cr * @throws IllegalArgumentException */ public static String getContainerNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { - return getContainerOrQueueNameFromUri(resourceAddress, usePathStyleUris, + return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid blob address '%s', missing container information", resourceAddress)); } /** - * Get the container or queue name from address from the URI. + * Get the table name from address from the URI. + * + * @param resourceAddress + * The table Uri. + * @param usePathStyleUris + * a value indicating if the address is a path style uri. + * @return table name from address from the URI. + * @throws IllegalArgumentException + */ + public static String getTableNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { + return getResourceNameFromUri(resourceAddress, usePathStyleUris, + String.format("Invalid table address '%s', missing table information", resourceAddress)); + } + + /** + * Get the container, queue or table name from address from the URI. * * @param resourceAddress * The queue Uri. @@ -210,7 +225,7 @@ public static String getContainerNameFromUri(final URI resourceAddress, final bo * @return container name from address from the URI. * @throws IllegalArgumentException */ - private static String getContainerOrQueueNameFromUri(final URI resourceAddress, final boolean usePathStyleUris, + private static String getResourceNameFromUri(final URI resourceAddress, final boolean usePathStyleUris, final String error) { Utility.assertNotNull("resourceAddress", resourceAddress); @@ -222,9 +237,9 @@ private static String getContainerOrQueueNameFromUri(final URI resourceAddress, throw new IllegalArgumentException(error); } - final String containerOrQueueName = usePathStyleUris ? pathSegments[2] : pathSegments[1]; + final String resourceName = usePathStyleUris ? pathSegments[2] : pathSegments[1]; - return Utility.trimEnd(containerOrQueueName, '/'); + return Utility.trimEnd(resourceName, '/'); } /** @@ -341,7 +356,7 @@ public static String getParentNameFromURI(final URI resourceAddress, final Strin * @throws IllegalArgumentException */ public static String getQueueNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { - return getContainerOrQueueNameFromUri(resourceAddress, usePathStyleUris, + return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid queue URI '%s'.", resourceAddress)); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java index 25611e73cf54..e47b64181ac9 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java @@ -16,6 +16,9 @@ import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,10 +26,16 @@ import javax.xml.stream.XMLStreamException; +import com.microsoft.windowsazure.services.blob.client.CopyState; +import com.microsoft.windowsazure.services.blob.client.CopyStatus; import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; +import com.microsoft.windowsazure.services.core.storage.LeaseStatus; import com.microsoft.windowsazure.services.core.storage.OperationContext; import com.microsoft.windowsazure.services.core.storage.ServiceProperties; import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; /** * RESERVED FOR INTERNAL USE. The base response class for the protocol layer @@ -75,6 +84,96 @@ public static HashMap getMetadata(final HttpURLConnection reques return getValuesByHeaderPrefix(request, Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA); } + /** + * Gets the LeaseStatus + * + * @param request + * The response from server. + * @return The Etag. + */ + public static LeaseStatus getLeaseStatus(final HttpURLConnection request) { + final String leaseStatus = request.getHeaderField(Constants.HeaderConstants.LEASE_STATUS); + if (!Utility.isNullOrEmpty(leaseStatus)) { + return LeaseStatus.parse(leaseStatus); + } + + return LeaseStatus.UNSPECIFIED; + } + + /** + * Gets the LeaseState + * + * @param request + * The response from server. + * @return The LeaseState. + */ + public static LeaseState getLeaseState(final HttpURLConnection request) { + final String leaseState = request.getHeaderField(Constants.HeaderConstants.LEASE_STATE); + if (!Utility.isNullOrEmpty(leaseState)) { + return LeaseState.parse(leaseState); + } + + return LeaseState.UNSPECIFIED; + } + + /** + * Gets the LeaseDuration + * + * @param request + * The response from server. + * @return The LeaseDuration. + */ + public static LeaseDuration getLeaseDuration(final HttpURLConnection request) { + final String leaseDuration = request.getHeaderField(Constants.HeaderConstants.LEASE_DURATION); + if (!Utility.isNullOrEmpty(leaseDuration)) { + return LeaseDuration.parse(leaseDuration); + } + + return LeaseDuration.UNSPECIFIED; + } + + /** + * Gets the copyState + * + * @param request + * The response from server. + * @return The CopyState. + * @throws URISyntaxException + * @throws ParseException + */ + public static CopyState getCopyState(final HttpURLConnection request) throws URISyntaxException, ParseException { + String copyStatusString = request.getHeaderField(Constants.HeaderConstants.COPY_STATUS); + if (!Utility.isNullOrEmpty(copyStatusString)) { + CopyState copyState = new CopyState(); + copyState.setStatus(CopyStatus.parse(copyStatusString)); + copyState.setCopyId(request.getHeaderField(Constants.HeaderConstants.COPY_ID)); + copyState.setStatusDescription(request.getHeaderField(Constants.HeaderConstants.COPY_STATUS_DESCRIPTION)); + + final String copyProgressString = request.getHeaderField(Constants.HeaderConstants.COPY_PROGRESS); + if (!Utility.isNullOrEmpty(copyProgressString)) { + String[] progressSequence = copyProgressString.split("/"); + copyState.setBytesCopied(Long.parseLong(progressSequence[0])); + copyState.setTotalBytes(Long.parseLong(progressSequence[1])); + } + + final String copySourceString = request.getHeaderField(Constants.HeaderConstants.COPY_SOURCE); + if (!Utility.isNullOrEmpty(copySourceString)) { + copyState.setSource(new URI(copySourceString)); + } + + final String copyCompletionTimeString = request + .getHeaderField(Constants.HeaderConstants.COPY_COMPLETION_TIME); + if (!Utility.isNullOrEmpty(copyCompletionTimeString)) { + copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(copyCompletionTimeString)); + } + + return copyState; + } + else { + return null; + } + } + /** * Gets the request id. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java index 17fe0633547b..d12785cbb8af 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java @@ -326,11 +326,10 @@ protected static String getCanonicalizedResourceLite(final java.net.URL address, if (stringValue.length() > 0) { stringValue.append(","); } - stringValue.append(value); } - appendCanonicalizedElement(canonicalizedResource, stringValue.toString()); + canonicalizedResource.append(stringValue); } return canonicalizedResource.toString(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java index e7bb775a2d71..b17a657c029d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java @@ -39,7 +39,12 @@ public enum LeaseAction { /** * Break the lease. */ - BREAK; + BREAK, + + /** + * Change the lease. + */ + CHANGE; @Override public String toString() { @@ -52,6 +57,8 @@ public String toString() { return "Release"; case BREAK: return "Break"; + case CHANGE: + return "Change"; default: // Wont Happen, all possible values covered above. return Constants.EMPTY_STRING; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java index 5e4ddf1ffdd7..f77e7931062e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java @@ -16,19 +16,24 @@ package com.microsoft.windowsazure.services.queue.client; import java.io.OutputStream; +import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; import com.microsoft.windowsazure.services.core.storage.StorageExtendedErrorInformation; import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseResponse; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; @@ -70,7 +75,7 @@ static CloudQueueMessage getFirstOrNull(final Iterable messag /** * A reference to the queue's associated service client. */ - private CloudQueueClient queueServiceClient; + CloudQueueClient queueServiceClient; /** * The queue's Metadata collection. @@ -103,8 +108,10 @@ static CloudQueueMessage getFirstOrNull(final Iterable messag * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - public CloudQueue(final String queueAddress, final CloudQueueClient client) throws URISyntaxException { + public CloudQueue(final String queueAddress, final CloudQueueClient client) throws URISyntaxException, + StorageException { this(PathUtility.appendPathToUri(client.getEndpoint(), queueAddress), client); } @@ -116,12 +123,15 @@ public CloudQueue(final String queueAddress, final CloudQueueClient client) thro * @param client * A {@link CloudQueueClient} object that represents the associated service client, and that specifies * the endpoint for the Queue service. + * @throws StorageException + * @throws URISyntaxException */ - public CloudQueue(final URI uri, final CloudQueueClient client) { + public CloudQueue(final URI uri, final CloudQueueClient client) throws URISyntaxException, StorageException { this.uri = uri; this.name = PathUtility.getQueueNameFromUri(uri, client.isUsePathStyleUris()); this.queueServiceClient = client; this.shouldEncodeMessage = true; + this.parseQueryAndVerify(this.uri, client, client.isUsePathStyleUris()); } /** @@ -200,8 +210,8 @@ public void addMessage(final CloudQueueMessage message, final int timeToLiveInSe public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.putMessage(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), initialVisibilityDelayInSeconds, + final HttpURLConnection request = QueueRequest.putMessage(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), initialVisibilityDelayInSeconds, timeToLiveInSeconds, opContext); final byte[] messageBytes = QueueRequest.generateMessageRequestBody(stringToSend); @@ -271,8 +281,8 @@ public void clear(QueueRequestOptions options, OperationContext opContext) throw public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.clearMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.clearMessages(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -335,8 +345,8 @@ public void create(QueueRequestOptions options, OperationContext opContext) thro @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.create(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.create(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -408,8 +418,8 @@ public boolean createIfNotExist(QueueRequestOptions options, OperationContext op @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.create(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.create(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -494,8 +504,8 @@ public void delete(QueueRequestOptions options, OperationContext opContext) thro @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.delete(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.delete(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -565,8 +575,8 @@ public boolean deleteIfExists(QueueRequestOptions options, OperationContext opCo @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.delete(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.delete(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -648,9 +658,9 @@ public void deleteMessage(final CloudQueueMessage message, QueueRequestOptions o public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.deleteMessage( - queue.getIndividualMessageAddress(messageId), - this.getRequestOptions().getTimeoutIntervalInMs(), messagePopReceipt, opContext); + final HttpURLConnection request = QueueRequest.deleteMessage(queue.getIndividualMessageAddress( + messageId, opContext), this.getRequestOptions().getTimeoutIntervalInMs(), messagePopReceipt, + opContext); client.getCredentials().signRequest(request, -1L); @@ -714,8 +724,9 @@ public void downloadAttributes(QueueRequestOptions options, OperationContext opC @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.downloadAttributes(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.downloadAttributes( + queue.getTransformedAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + opContext); client.getCredentials().signRequest(request, -1L); @@ -786,8 +797,9 @@ public boolean exists(QueueRequestOptions options, OperationContext opContext) t @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.downloadAttributes(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.downloadAttributes( + queue.getTransformedAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + opContext); client.getCredentials().signRequest(request, -1L); @@ -828,9 +840,11 @@ public long getApproximateMessageCount() { * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - URI getIndividualMessageAddress(final String messageId) throws URISyntaxException { - return PathUtility.appendPathToUri(this.messageRequestAddress, messageId); + URI getIndividualMessageAddress(final String messageId, final OperationContext opContext) + throws URISyntaxException, StorageException { + return PathUtility.appendPathToUri(this.getMessageRequestAddress(opContext), messageId); } /** @@ -840,10 +854,12 @@ URI getIndividualMessageAddress(final String messageId) throws URISyntaxExceptio * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - URI getMessageRequestAddress() throws URISyntaxException { + URI getMessageRequestAddress(final OperationContext opContext) throws URISyntaxException, StorageException { if (this.messageRequestAddress == null) { - this.messageRequestAddress = PathUtility.appendPathToUri(this.uri, QueueConstants.MESSAGES); + this.messageRequestAddress = PathUtility.appendPathToUri(this.getTransformedAddress(opContext), + QueueConstants.MESSAGES); } return this.messageRequestAddress; @@ -997,8 +1013,8 @@ public Iterable peekMessages(final int numberOfMessages, Queu public ArrayList execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.peekMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, opContext); + final HttpURLConnection request = QueueRequest.peekMessages(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, opContext); client.getCredentials().signRequest(request, -1L); @@ -1130,9 +1146,9 @@ public Iterable retrieveMessages(final int numberOfMessages, public ArrayList execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.retrieveMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, visibilityTimeoutInSeconds, - opContext); + final HttpURLConnection request = QueueRequest.retrieveMessages( + queue.getMessageRequestAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + numberOfMessages, visibilityTimeoutInSeconds, opContext); client.getCredentials().signRequest(request, -1L); @@ -1258,9 +1274,9 @@ public void updateMessage(final CloudQueueMessage message, final int visibilityT public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.updateMessage(queue.getIndividualMessageAddress(message - .getId()), this.getRequestOptions().getTimeoutIntervalInMs(), message.getPopReceipt(), - visibilityTimeoutInSeconds, opContext); + final HttpURLConnection request = QueueRequest.updateMessage(queue.getIndividualMessageAddress( + message.getId(), opContext), this.getRequestOptions().getTimeoutIntervalInMs(), message + .getPopReceipt(), visibilityTimeoutInSeconds, opContext); if (messageUpdateFields.contains(MessageUpdateFields.CONTENT)) { final byte[] messageBytes = QueueRequest.generateMessageRequestBody(stringToSend); @@ -1341,8 +1357,8 @@ public void uploadMetadata(QueueRequestOptions options, OperationContext opConte public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.setMetadata(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.setMetadata(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -1361,4 +1377,290 @@ public Void execute(final CloudQueueClient client, final CloudQueue queue, final opContext); } + + /** + * Uploads the queue's permissions. + * + * @param permissions + * A {@link QueuePermissions} object that represents the permissions to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final QueuePermissions permissions) throws StorageException { + this.uploadPermissions(permissions, null, null); + } + + /** + * Uploads the queue's permissions using the specified request options and operation context. + * + * @param permissions + * A {@link QueuePermissions} object that represents the permissions to upload. + * @param options + * A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudQueueClient}). + * @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 uploadPermissions(final QueuePermissions permissions, QueueRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new QueueRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.queueServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + + @Override + public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) + throws Exception { + + final HttpURLConnection request = QueueRequest.setAcl(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); + + final StringWriter outBuffer = new StringWriter(); + + QueueRequest.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(), outBuffer); + + final byte[] aclBytes = outBuffer.toString().getBytes("UTF8"); + client.getCredentials().signRequest(request, aclBytes.length); + final OutputStream outStreamRef = request.getOutputStream(); + outStreamRef.write(aclBytes); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + this.setNonExceptionedRetryableFailure(true); + } + + return null; + } + }; + + ExecutionEngine.executeWithRetry(this.queueServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Downloads the permission settings for the queue. + * + * @return A {@link QueuePermissions} object that represents the queue's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public QueuePermissions downloadPermissions() throws StorageException { + return this.downloadPermissions(null, null); + } + + /** + * Downloads the permissions settings for the queue using the specified request options and operation context. + * + * @param options + * A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudQueueClient}). + * @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 QueuePermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public QueuePermissions downloadPermissions(QueueRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new QueueRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.queueServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + + @Override + public QueuePermissions execute(final CloudQueueClient client, final CloudQueue queue, + final OperationContext opContext) throws Exception { + + final HttpURLConnection request = QueueRequest.getAcl(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); + + client.getCredentials().signRequest(request, -1L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + final QueuePermissions permissions = new QueuePermissions(); + final QueueAccessPolicyResponse response = new QueueAccessPolicyResponse(request.getInputStream()); + + for (final String key : response.getAccessIdentifiers().keySet()) { + permissions.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); + } + + return permissions; + } + }; + + return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Returns a shared access signature for the queue. + * + * @param policy + * The access policy for the shared access signature. + * @param groupPolicyIdentifier + * A queue-level access policy. + * @return a shared access signature for the container. + * @throws InvalidKeyException + * @throws StorageException + * @throws IllegalArgumentException + */ + public String generateSharedAccessSignature(final SharedAccessQueuePolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { + + if (!this.queueServiceClient.getCredentials().canCredentialsSignRequest()) { + final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the QueueServiceClient."; + throw new IllegalArgumentException(errorMessage); + } + + final String resourceName = this.getSharedAccessCanonicalName(); + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, + groupPolicyIdentifier, resourceName, this.queueServiceClient, null); + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, + groupPolicyIdentifier, signature); + + return builder.toString(); + } + + /** + * Returns the canonical name for shared access. + * + * @return the canonical name for shared access. + */ + private String getSharedAccessCanonicalName() { + if (this.queueServiceClient.isUsePathStyleUris()) { + return this.getUri().getPath(); + } + else { + return PathUtility.getCanonicalPathFromCredentials(this.queueServiceClient.getCredentials(), this.getUri() + .getPath()); + } + } + + /** + * Returns the transformed URI for the resource if the given credentials require transformation. + * + * @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 java.net.URI object that represents the transformed URI. + * + * @throws IllegalArgumentException + * If the URI is not absolute. + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + protected final URI getTransformedAddress(final OperationContext opContext) throws URISyntaxException, + StorageException { + if (this.queueServiceClient.getCredentials().doCredentialsNeedTransformUri()) { + if (this.getUri().isAbsolute()) { + return this.queueServiceClient.getCredentials().transformUri(this.getUri(), opContext); + } + else { + final StorageException ex = Utility.generateNewUnexpectedStorageException(null); + ex.getExtendedErrorInformation().setErrorMessage("Queue Object relative URIs not supported."); + throw ex; + } + } + else { + return this.getUri(); + } + } + + /** + * Parse Uri for SAS (Shared access signature) information. + * + * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding + * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise + * a new client is created based on SAS information or as anonymous credentials. + * + * @param completeUri + * The complete Uri. + * @param existingClient + * The client to use. + * @param usePathStyleUris + * If true, path style Uris are used. + * @throws URISyntaxException + * @throws StorageException + */ + private void parseQueryAndVerify(final URI completeUri, final CloudQueueClient existingClient, + final boolean usePathStyleUris) throws URISyntaxException, StorageException { + Utility.assertNotNull("completeUri", completeUri); + + if (!completeUri.isAbsolute()) { + final String errorMessage = String.format( + "Address '%s' is not an absolute address. Relative addresses are not permitted in here.", + completeUri.toString()); + throw new IllegalArgumentException(errorMessage); + } + + this.uri = PathUtility.stripURIQueryAndFragment(completeUri); + + final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper + .parseQuery(queryParameters); + + if (sasCreds == null) { + return; + } + + final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, + existingClient.getCredentials()); + + if (existingClient == null || !sameCredentials) { + this.queueServiceClient = new CloudQueueClient(new URI(PathUtility.getServiceClientBaseAddress( + this.getUri(), usePathStyleUris)), sasCreds); + } + + if (existingClient != null && !sameCredentials) { + this.queueServiceClient.setRetryPolicyFactory(existingClient.getRetryPolicyFactory()); + this.queueServiceClient.setTimeoutInMs(existingClient.getTimeoutInMs()); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java new file mode 100644 index 000000000000..457113684d9d --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.io.InputStream; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class QueueAccessPolicyResponse extends AccessPolicyResponseBase { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public QueueAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessQueuePolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessQueuePolicy retPolicy = new SharedAccessQueuePolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessQueuePolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java new file mode 100644 index 000000000000..6454f1b86fe8 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java @@ -0,0 +1,57 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.util.HashMap; + +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePolicy; + +/** + * Represents the permissions for a container. + */ + +public final class QueuePermissions { + + /** + * Gets the set of shared access policies for the table. + */ + private HashMap sharedAccessPolicies; + + /** + * Creates an instance of the TablePermissions class. + */ + public QueuePermissions() { + this.sharedAccessPolicies = new HashMap(); + } + + /** + * Returns the set of shared access policies for the table. + * + * @return A HashMap object of {@link SharedAccessTablePolicy} objects that represent the set of shared + * access policies for the table. + */ + public HashMap getSharedAccessPolicies() { + return this.sharedAccessPolicies; + } + + /** + * @param sharedAccessPolicies + * the sharedAccessPolicies to set + */ + public void setSharedAccessPolicies(final HashMap sharedAccessPolicies) { + this.sharedAccessPolicies = sharedAccessPolicies; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java index 1b8867dc1797..cf0c6316a0c6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java @@ -22,6 +22,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.Map.Entry; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; @@ -612,6 +613,124 @@ public static HttpURLConnection updateMessage(final URI uri, final int timeout, return request; } + /** + * Sets the ACL for the queue. , Sign with length of aclBytes. + * + * @param uri + * The absolute URI to the queue. + * @param timeout + * The server timeout interval. + * @param publicAccess + * The type of public access to allow for the queue. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * */ + public static HttpURLConnection setAcl(final URI uri, final int timeout, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add("comp", "acl"); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setDoOutput(true); + request.setRequestMethod("PUT"); + + return request; + } + + /** + * Writes a collection of shared access policies to the specified stream in XML format. + * + * @param sharedAccessPolicies + * A collection of shared access policies + * @param outWriter + * an sink to write the output to. + * @throws XMLStreamException + */ + public static void writeSharedAccessIdentifiersToStream( + final HashMap sharedAccessPolicies, final StringWriter outWriter) + throws XMLStreamException { + Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); + Utility.assertNotNull("outWriter", outWriter); + + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); + + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + final String errorMessage = String + .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single queue.", + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + + throw new IllegalArgumentException(errorMessage); + } + + // default is UTF8 + xmlw.writeStartDocument(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); + + for (final Entry entry : sharedAccessPolicies.entrySet()) { + final SharedAccessQueuePolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); + + // Set the identifier + xmlw.writeStartElement(Constants.ID); + xmlw.writeCharacters(entry.getKey()); + xmlw.writeEndElement(); + + xmlw.writeStartElement(Constants.ACCESS_POLICY); + + // Set the Start Time + xmlw.writeStartElement(Constants.START); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); + // end Start + xmlw.writeEndElement(); + + // Set the Expiry Time + xmlw.writeStartElement(Constants.EXPIRY); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); + // end Expiry + xmlw.writeEndElement(); + + // Set the Permissions + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessQueuePolicy.permissionsToString(policy.getPermissions())); + // end Permission + xmlw.writeEndElement(); + + // end AccessPolicy + xmlw.writeEndElement(); + // end SignedIdentifier + xmlw.writeEndElement(); + } + + // end SignedIdentifiers + xmlw.writeEndElement(); + // end doc + xmlw.writeEndDocument(); + } + + /** + * Constructs a web request to return the ACL for this queue. Sign with no length specified. + * + * @param uri + * The absolute URI to the container. + * @param timeout + * The server timeout interval. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getAcl(final URI uri, final int timeout, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add("comp", "acl"); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setRequestMethod("GET"); + + return request; + } + /** * Private Default Ctor. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java new file mode 100644 index 000000000000..bec37d8d33d1 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.util.EnumSet; + +/** + * Specifies the set of possible permissions for a shared access queue policy. + */ +public enum SharedAccessQueuePermissions { + + /** + * No shared access granted. + */ + NONE((byte) 0x0), + + /** + * Permission to peek messages and get queue metadata granted. + */ + READ((byte) 0x1), + + /** + * Permission to add messages granted. + */ + ADD((byte) 0x2), + + /** + * Permissions to update messages granted. + */ + UPDATE((byte) 0x4), + + /** + * Permission to get and delete messages granted. + */ + PROCESSMESSAGES((byte) 0x8); + + /** + * Returns the enum set representing the shared access permissions for the specified byte value. + * + * @param value + * The byte value to convert to the corresponding enum set. + * @return A java.util.EnumSet object that contains the SharedAccessPermissions values + * corresponding to the specified byte value. + */ + protected static EnumSet fromByte(final byte value) { + final EnumSet retSet = EnumSet.noneOf(SharedAccessQueuePermissions.class); + + if (value == READ.value) { + retSet.add(READ); + } + + if (value == PROCESSMESSAGES.value) { + retSet.add(PROCESSMESSAGES); + } + if (value == ADD.value) { + retSet.add(ADD); + } + if (value == UPDATE.value) { + retSet.add(UPDATE); + } + + return retSet; + } + + /** + * Returns the value of this enum. + */ + private byte value; + + /** + * Sets the value of this enum. + * + * @param val + * The value being assigned. + */ + SharedAccessQueuePermissions(final byte val) { + this.value = val; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java new file mode 100644 index 000000000000..54bdf677ad44 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java @@ -0,0 +1,174 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.util.Date; +import java.util.EnumSet; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePermissions; + +/** + * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access + * signature. + */ +public final class SharedAccessQueuePolicy { + + /** + * Assigns shared access permissions using the specified permissions string. + * + * @param value + * A String that represents the shared access permissions. The string must contain one or + * more of the following values. Note they must be lowercase, and the order that they are specified must + * be in the order of "rwdl". + *
      + *
    • d: Delete access.
    • + *
    • l: List access.
    • + *
    • r: Read access.
    • + *
    • w: Write access.
    • + *
    + * + * @return A java.util.EnumSet object that contains {@link SharedAccessTablePermissions} values that + * represents the set of shared access permissions. + */ + public static EnumSet permissionsFromString(final String value) { + final char[] chars = value.toCharArray(); + final EnumSet retSet = EnumSet.noneOf(SharedAccessQueuePermissions.class); + + for (final char c : chars) { + switch (c) { + case 'r': + retSet.add(SharedAccessQueuePermissions.READ); + break; + case 'a': + retSet.add(SharedAccessQueuePermissions.ADD); + break; + case 'u': + retSet.add(SharedAccessQueuePermissions.UPDATE); + break; + case 'p': + retSet.add(SharedAccessQueuePermissions.PROCESSMESSAGES); + break; + default: + throw new IllegalArgumentException("value"); + } + } + + return retSet; + } + + /** + * Converts the permissions specified for the shared access policy to a string. + * + * @param permissions + * A {@link SharedAccessQueuePermissions} object that represents the shared access permissions. + * + * @return A String that represents the shared access permissions in the "rwdl" format, which is + * described at {@link SharedAccessQueuePermissions#permissionsFromString}. + */ + public static String permissionsToString(final EnumSet permissions) { + if (permissions == null) { + return Constants.EMPTY_STRING; + } + + // The service supports a fixed order => rwdl + final StringBuilder builder = new StringBuilder(); + + if (permissions.contains(SharedAccessQueuePermissions.READ)) { + builder.append("r"); + } + + if (permissions.contains(SharedAccessQueuePermissions.ADD)) { + builder.append("a"); + } + + if (permissions.contains(SharedAccessQueuePermissions.UPDATE)) { + builder.append("u"); + } + + if (permissions.contains(SharedAccessQueuePermissions.PROCESSMESSAGES)) { + builder.append("p"); + } + + return builder.toString(); + } + + /** + * The permissions for a shared access signature associated with this shared access policy. + */ + private EnumSet permissions; + + /** + * The expiry time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessExpiryTime; + + /** + * The start time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessStartTime; + + /** + * Creates an instance of the SharedAccessTablePolicy class. + * */ + public SharedAccessQueuePolicy() { + // Empty Default Ctor + } + + /** + * @return the permissions + */ + public EnumSet getPermissions() { + return this.permissions; + } + + /** + * @return the sharedAccessExpiryTime + */ + public Date getSharedAccessExpiryTime() { + return this.sharedAccessExpiryTime; + } + + /** + * @return the sharedAccessStartTime + */ + public Date getSharedAccessStartTime() { + return this.sharedAccessStartTime; + } + + /** + * @param permissions + * the permissions to set + */ + public void setPermissions(final EnumSet permissions) { + this.permissions = permissions; + } + + /** + * @param sharedAccessExpiryTime + * the sharedAccessExpiryTime to set + */ + public void setSharedAccessExpiryTime(final Date sharedAccessExpiryTime) { + this.sharedAccessExpiryTime = sharedAccessExpiryTime; + } + + /** + * @param sharedAccessStartTime + * the sharedAccessStartTime to set + */ + public void setSharedAccessStartTime(final Date sharedAccessStartTime) { + this.sharedAccessStartTime = sharedAccessStartTime; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java new file mode 100644 index 000000000000..2ec64eceaf53 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java @@ -0,0 +1,643 @@ +/** + * Copyright 2011 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.windowsazure.services.table.client; + +import java.io.OutputStream; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; + +import com.microsoft.windowsazure.services.blob.client.BlobContainerPermissions; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; +import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentials; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsAccountAndKey; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; + +/** + * Represents a table in the Windows Azure Table service. + */ +public final class CloudTable { + + /** + * Holds the name of the table. + */ + String name; + + /** + * Holds the URI of the table. + */ + URI uri; + + /** + * Holds a reference to the associated service client. + */ + private final CloudTableClient tableServiceClient; + + /** + * Gets the name of the queue. + * + * @return A String object that represents the name of the queue. + */ + public String getName() { + return this.name; + } + + /** + * Gets the table service client associated with this queue. + * + * @return A {@link CloudTableClient} object that represents the service client associated with this table. + */ + public CloudTableClient getServiceClient() { + return this.tableServiceClient; + } + + /** + * Gets the absolute URI for this queue. + * + * @return A java.net.URI object that represents the URI for this queue. + */ + public URI getUri() { + return this.uri; + } + + /** + * Creates an instance of the CloudTable class using the specified address and client. + * + * @param tableAddress + * A String that represents the table name. + * @param client + * A {@link CloudTableClient} object that represents the associated service client, and that specifies + * the endpoint for the Table service. + * + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudTable(final String tableName, final CloudTableClient client) throws URISyntaxException { + this(PathUtility.appendPathToUri(client.getEndpoint(), tableName), client); + } + + /** + * Creates an instance of the CloudTable class using the specified table URI and client. + * + * @param uri + * A java.net.URI object that represents the absolute URI of the table. + * @param client + * A {@link CloudTableClient} object that represents the associated service client, and that specifies + * the endpoint for the Table service. + */ + public CloudTable(final URI uri, final CloudTableClient client) { + this.uri = uri; + this.name = PathUtility.getTableNameFromUri(uri, client.isUsePathStyleUris()); + this.tableServiceClient = client; + } + + /** + * Creates the table in the storage service with default request options. + *

    + * This method invokes the Create + * Table REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public void create() throws StorageException { + this.create(null, null); + } + + /** + * Creates the table in the storage service, using the specified {@link TableRequestOptions} and + * {@link OperationContext}. + *

    + * This method invokes the Create + * Table REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param options + * A {@link TableRequestOptions} 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 CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify null to + * safely ignore operation context. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table cannot be + * created, or already exists. + */ + @DoesServiceRequest + public void create(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name)); + + this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, TableOperation.insert(tableEntry), + options, opContext); + } + + /** + * Creates the table in the storage service using default request options if it does not already exist. + * + * @return A value of true if the table is created in the storage service, otherwise false + * . + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean createIfNotExist() throws StorageException { + return this.createIfNotExist(null, null); + } + + /** + * Creates the table in the storage service with the specified request options and operation context if it does not + * already exist. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 value of true if the table is created in the storage service, otherwise false + * . + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean createIfNotExist(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + if (this.exists(options, opContext)) { + return false; + } + else { + try { + this.create(options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT + && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + } + + /** + * Deletes the table from the storage service. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public void delete() throws StorageException { + this.delete(null, null); + } + + /** + * Deletes the table from the storage service, using the specified request options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 during the operation. + */ + @DoesServiceRequest + public void delete(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name)); + + final TableOperation delOp = new TableOperation(tableEntry, TableOperationType.DELETE); + + final TableResult result = this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, delOp, + options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { + return; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + + } + + /** + * Deletes the table from the storage service if it exists. + * + * @return A value of true if the table existed in the storage service and has been deleted, otherwise + * false. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean deleteIfExists() throws StorageException { + return this.deleteIfExists(null, null); + } + + /** + * Deletes the table from the storage service using the specified request options and operation context, if it + * exists. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 value of true if the table existed in the storage service and has been deleted, otherwise + * false. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean deleteIfExists(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + if (this.exists(options, opContext)) { + try { + this.delete(options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND + && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + else { + return false; + } + + } + + /** + * Returns a value that indicates whether the table exists in the storage service. + * + * @return true if the table exists in the storage service, otherwise false. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean exists() throws StorageException { + return this.exists(null, null); + } + + /** + * Returns a value that indicates whether the table exists in the storage service, using the specified request + * options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 table exists in the storage service, otherwise false. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean exists(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + final TableResult result = this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, + TableOperation.retrieve(this.name /* Used As PK */, null/* Row Key */, DynamicTableEntity.class), + options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) { + return true; + } + else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + } + + /** + * Uploads the table's permissions. + * + * @param permissions + * A {@link TablePermissions} object that represents the permissions to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final TablePermissions permissions) throws StorageException { + this.uploadPermissions(permissions, null, null); + } + + /** + * Uploads the container's permissions using the specified request options and operation context. + * + * @param permissions + * A {@link TablePermissions} object that represents the permissions to upload. + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 uploadPermissions(final TablePermissions permissions, TableRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + final StorageOperation impl = new StorageOperation( + options) { + + @Override + public Void execute(final CloudTableClient client, final CloudTable table, final OperationContext opContext) + throws Exception { + + final HttpURLConnection request = TableRequest.setAcl(table.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), opContext); + + final StringWriter outBuffer = new StringWriter(); + + TableRequest.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(), outBuffer); + + final byte[] aclBytes = outBuffer.toString().getBytes("UTF8"); + client.getCredentials().signRequestLite(request, aclBytes.length, opContext); + final OutputStream outStreamRef = request.getOutputStream(); + outStreamRef.write(aclBytes); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + this.setNonExceptionedRetryableFailure(true); + } + + return null; + } + }; + + ExecutionEngine.executeWithRetry(this.tableServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Downloads the permission settings for the table. + * + * @return A {@link TablePermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public TablePermissions downloadPermissions() throws StorageException { + return this.downloadPermissions(null, null); + } + + /** + * Downloads the permissions settings for the table using the specified request options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @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 BlobContainerPermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public TablePermissions downloadPermissions(TableRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + final String tableName = this.name; + + final StorageOperation impl = new StorageOperation( + options) { + + @Override + public TablePermissions execute(final CloudTableClient client, final CloudTable table, + final OperationContext opContext) throws Exception { + + final HttpURLConnection request = TableRequest.getAcl(table.uri, tableName, this.getRequestOptions() + .getTimeoutIntervalInMs(), opContext); + + client.getCredentials().signRequestLite(request, -1L, opContext); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + final TablePermissions permissions = new TablePermissions(); + final TableAccessPolicyResponse response = new TableAccessPolicyResponse(request.getInputStream()); + for (final String key : response.getAccessIdentifiers().keySet()) { + permissions.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); + } + + return permissions; + } + }; + + return ExecutionEngine.executeWithRetry(this.tableServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Returns a shared access signature for the table. + * + * @param policy + * The access policy for the shared access signature. + * @param accessPolicyIdentifier + * A table-level access policy. + * @return a shared access signature for the container. + * @throws InvalidKeyException + * @throws StorageException + * @throws IllegalArgumentException + */ + public String generateSharedAccessSignature(final SharedAccessTablePolicy policy, + final String accessPolicyIdentifier, final String startPartitionKey, final String startRowKey, + final String endPartitionKey, final String endRowKey) throws InvalidKeyException, StorageException { + + if (!this.tableServiceClient.getCredentials().canCredentialsSignRequest()) { + final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the BlobServiceClient."; + throw new IllegalArgumentException(errorMessage); + } + + final String resourceName = this.getSharedAccessCanonicalName(); + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, + accessPolicyIdentifier, resourceName, startPartitionKey, startRowKey, endPartitionKey, endRowKey, + this.tableServiceClient, null); + + String accountKeyName = null; + StorageCredentials credentials = this.tableServiceClient.getCredentials(); + + if (credentials instanceof StorageCredentialsAccountAndKey) { + accountKeyName = ((StorageCredentialsAccountAndKey) credentials).getAccountKeyName(); + } + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, + startPartitionKey, startRowKey, endPartitionKey, endRowKey, accessPolicyIdentifier, this.name, + signature, accountKeyName); + + return builder.toString(); + } + + /** + * Returns the canonical name for shared access. + * + * @return the canonical name for shared access. + */ + private String getSharedAccessCanonicalName() { + if (this.tableServiceClient.isUsePathStyleUris()) { + return this.getUri().getPath(); + } + else { + return PathUtility.getCanonicalPathFromCredentials(this.tableServiceClient.getCredentials(), this.getUri() + .getPath()); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java index 00075de49192..4420c2d37518 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java @@ -35,13 +35,13 @@ import com.microsoft.windowsazure.services.core.storage.ResultSegment; import com.microsoft.windowsazure.services.core.storage.ServiceClient; import com.microsoft.windowsazure.services.core.storage.StorageCredentials; -import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; +import com.microsoft.windowsazure.services.queue.client.CloudQueue; /** * Provides a service client for accessing the Windows Azure Table service. @@ -108,400 +108,24 @@ public CloudTableClient(final URI baseUri, StorageCredentials credentials) { } /** - * Creates a table with the specified name in the storage service. - *

    - * This method invokes the Create - * Table REST API to create the specified table, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A String object containing the name of the table to create. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table cannot be - * created, or already exists. - */ - @DoesServiceRequest - public void createTable(final String tableName) throws StorageException { - this.createTable(tableName, null, null); - } - - /** - * Creates a table with the specified name in the storage service, using the specified {@link TableRequestOptions} - * and {@link OperationContext}. - *

    - * This method invokes the Create - * Table REST API to create the specified table, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A String object containing the name of the table to create. - * @param options - * A {@link TableRequestOptions} 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 CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify null to - * safely ignore operation context. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table cannot be - * created, or already exists. - */ - @DoesServiceRequest - public void createTable(final String tableName, TableRequestOptions options, OperationContext opContext) - throws StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - if (options == null) { - options = new TableRequestOptions(); - } - - opContext.initialize(); - options.applyDefaults(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - final DynamicTableEntity tableEntry = new DynamicTableEntity(); - tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); - - this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, TableOperation.insert(tableEntry), options, opContext); - } - - /** - * Creates a table with the specified name in the storage service, if it does not already exist. - *

    - * This method first invokes the Query - * Tables REST API to determine if the table exists, and if not, invokes the Create Table Storage Service REST - * API to create the specified table, using the Table service endpoint and storage account credentials of this - * instance. - * - * @param tableName - * A String object containing the name of the table to create. - * - * @return - * A value of true if the operation created a new table, otherwise false. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table does not - * exist and cannot be created. - */ - @DoesServiceRequest - public boolean createTableIfNotExists(final String tableName) throws StorageException { - return this.createTableIfNotExists(tableName, null, null); - } - - /** - * Creates a table with the specified name in the storage service if it does not already exist, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - *

    - * This method first invokes the Query - * Tables REST API to determine if the table exists, and if not, invokes the Create Table Storage Service REST - * API to create the specified table, using the Table service endpoint and storage account credentials of this - * instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A String object containing the name of the table to create. - * @param options - * A {@link TableRequestOptions} 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 CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify null to - * safely ignore operation context. - * - * @return - * A value of true if the operation created a new table, otherwise false. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table does not - * exist and cannot be created. - */ - @DoesServiceRequest - public boolean createTableIfNotExists(final String tableName, TableRequestOptions options, - OperationContext opContext) throws StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - if (options == null) { - options = new TableRequestOptions(); - } - - opContext.initialize(); - options.applyDefaults(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - if (this.doesTableExist(tableName, options, opContext)) { - return false; - } - else { - try { - this.createTable(tableName, options, opContext); - } - catch (StorageException ex) { - if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT - && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(ex.getErrorCode())) { - return false; - } - else { - throw ex; - } - } - return true; - } - } - - /** - * Deletes the table with the specified name in the storage service. - *

    - * This method invokes the Delete - * Table REST API to delete the specified table and any data it contains, using the Table service endpoint and - * storage account credentials of this instance. + * Gets a {@link CloudTable} object that represents the storage service + * queue for the specified address. * - * @param tableName - * A String object containing the name of the table to delete. + * @param tableAddress + * A String that represents the name of the table, + * or the absolute URI to the queue. * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public void deleteTable(final String tableName) throws StorageException { - this.deleteTable(tableName, null, null); - } - - /** - * Deletes the table with the specified name in the storage service, using the specified {@link TableRequestOptions} - * and {@link OperationContext}. - *

    - * This method invokes the Delete - * Table REST API to delete the specified table and any data it contains, using the Table service endpoint and - * storage account credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A String object containing the name of the table to delete. - * @param options - * A {@link TableRequestOptions} 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 CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify null to - * safely ignore operation context. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public void deleteTable(final String tableName, TableRequestOptions options, OperationContext opContext) - throws StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - if (options == null) { - options = new TableRequestOptions(); - } - - opContext.initialize(); - options.applyDefaults(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - final DynamicTableEntity tableEntry = new DynamicTableEntity(); - tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); - - final TableOperation delOp = new TableOperation(tableEntry, TableOperationType.DELETE); - - final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, delOp, options, opContext); - - if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return; - } - else { - throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, - "Unexpected http status code received.", result.getHttpStatusCode(), null, null); - } - } - - /** - * Deletes the table with the specified name in the storage service, if it exists. - *

    - * This method first invokes the Query - * Tables REST API to determine if the table exists, and if so, invokes the Delete Table Storage Service REST - * API to delete the table and any data it contains, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A String object containing the name of the table to delete. - * - * @return - * A value of true if the operation deleted an existing table, otherwise false. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public boolean deleteTableIfExists(final String tableName) throws StorageException { - return this.deleteTableIfExists(tableName, null, null); - } - - /** - * Deletes the table with the specified name in the storage service, if it exists, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - *

    - * This method first invokes the Query - * Tables REST API to determine if the table exists, and if so, invokes the Delete Table Storage Service REST - * API to delete the table and any data it contains, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A String object containing the name of the table to delete. - * @param options - * A {@link TableRequestOptions} 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 CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify null to - * safely ignore operation context. - * - * @return - * A value of true if the operation deleted an existing table, otherwise false. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public boolean deleteTableIfExists(final String tableName, TableRequestOptions options, OperationContext opContext) - throws StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - if (options == null) { - options = new TableRequestOptions(); - } - - opContext.initialize(); - options.applyDefaults(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - if (this.doesTableExist(tableName, options, opContext)) { - try { - this.deleteTable(tableName, options, opContext); - } - catch (StorageException ex) { - if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND - && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) { - return false; - } - else { - throw ex; - } - } - return true; - } - else { - return false; - } - } - - /** - * Determines if a table with the specified name exists in the storage service. - *

    - * This method invokes the Query - * Tables REST API to determine if the table exists, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A String object containing the name of the table to find. - * - * @return - * A value of true if the table exists, otherwise false. - * - * @throws StorageException - * if an error occurs accessing the storage service. - */ - @DoesServiceRequest - public boolean doesTableExist(final String tableName) throws StorageException { - return this.doesTableExist(tableName, null, null); - } - - /** - * Determines if a table with the specified name exists in the storage service, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - *

    - * This method invokes the Query - * Tables REST API to determine if the table exists, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A String object containing the name of the table to find. - * @param options - * A {@link TableRequestOptions} 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 CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify null to - * safely ignore operation context. - * - * @return - * A value of true if the table exists, otherwise false. + * @return A {@link CloudQueue} object that represents a reference to the + * table. * + * @throws URISyntaxException + * If the resource URI is invalid. * @throws StorageException - * if an error occurs accessing the storage service. + * If a storage service error occurred during the operation. */ - @DoesServiceRequest - public boolean doesTableExist(final String tableName, TableRequestOptions options, OperationContext opContext) - throws StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - if (options == null) { - options = new TableRequestOptions(); - } - - opContext.initialize(); - options.applyDefaults(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, - TableOperation.retrieve(tableName /* Used As PK */, null/* Row Key */, DynamicTableEntity.class), - options, opContext); - - if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) { - return true; - } - else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { - return false; - } - else { - throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, - "Unexpected http status code received.", result.getHttpStatusCode(), null, null); - } + public CloudTable getTableReference(final String tableAddress) throws URISyntaxException, StorageException { + Utility.assertNotNullOrEmpty("tableAddress", tableAddress); + return new CloudTable(tableAddress, this); } /** @@ -1170,7 +794,7 @@ protected ResultSegment executeQuerySegmentedCore( Utility.assertNotNull("Query requires a valid class type or resolver.", queryToExecute.getClazzType()); } - final HttpURLConnection queryRequest = TableRequest.query(this.getEndpoint(), + final HttpURLConnection queryRequest = TableRequest.query(this.getTransformedEndPoint(opContext), queryToExecute.getSourceTableName(), null/* identity */, options.getTimeoutIntervalInMs(), queryToExecute.generateQueryBuilder(), continuationToken, options, opContext); @@ -1270,6 +894,23 @@ public ResultSegment execute(final CloudTableClient client, final TableQuery< return ExecutionEngine.executeWithRetry(this, queryToExecute, impl, options.getRetryPolicyFactory(), opContext); } + protected final URI getTransformedEndPoint(final OperationContext opContext) throws URISyntaxException, + StorageException { + if (this.getCredentials().doCredentialsNeedTransformUri()) { + if (this.getEndpoint().isAbsolute()) { + return this.getCredentials().transformUri(this.getEndpoint(), opContext); + } + else { + final StorageException ex = Utility.generateNewUnexpectedStorageException(null); + ex.getExtendedErrorInformation().setErrorMessage("Table Object relative URIs not supported."); + throw ex; + } + } + else { + return this.getEndpoint(); + } + } + /** * Reserved for internal use. Generates an iterator for a segmented query operation. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java index 2f02f4bb50e2..74098b013074 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java @@ -182,8 +182,8 @@ protected TableResult performRetrieve(final CloudTableClient client, final Strin public TableResult execute(final CloudTableClient client, final QueryTableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.query(client.getEndpoint(), tableName, - generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), + final HttpURLConnection request = TableRequest.query(client.getTransformedEndPoint(opContext), + tableName, generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), options.getTimeoutIntervalInMs(), null/* Query Builder */, null/* Continuation Token */, options, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java new file mode 100644 index 000000000000..3e75ac8e196c --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 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.windowsazure.services.table.client; + +import java.util.EnumSet; + +/** + * Specifies the set of possible permissions for a shared access table policy. + */ +public enum SharedAccessTablePermissions { + + /** + * No shared access granted. + */ + NONE((byte) 0x0), + + /** + * Permission to query entities granted. + */ + QUERY((byte) 0x1), + + /** + * Permission to add entities granted. + */ + ADD((byte) 0x2), + + /** + * Permission to modify entities granted. + */ + UPDATE((byte) 0x4), + + /** + * Permission to delete entities granted. + */ + DELETE((byte) 0x8); + + /** + * Returns the enum set representing the shared access permissions for the specified byte value. + * + * @param value + * The byte value to convert to the corresponding enum set. + * @return A java.util.EnumSet object that contains the SharedAccessPermissions values + * corresponding to the specified byte value. + */ + protected static EnumSet fromByte(final byte value) { + final EnumSet retSet = EnumSet.noneOf(SharedAccessTablePermissions.class); + + if (value == QUERY.value) { + retSet.add(QUERY); + } + + if (value == ADD.value) { + retSet.add(ADD); + } + if (value == UPDATE.value) { + retSet.add(UPDATE); + } + if (value == DELETE.value) { + retSet.add(DELETE); + } + + return retSet; + } + + /** + * Returns the value of this enum. + */ + private byte value; + + /** + * Sets the value of this enum. + * + * @param val + * The value being assigned. + */ + SharedAccessTablePermissions(final byte val) { + this.value = val; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java new file mode 100644 index 000000000000..adfaffb35385 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java @@ -0,0 +1,173 @@ +/** + * Copyright 2011 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.windowsazure.services.table.client; + +import java.util.Date; +import java.util.EnumSet; + +import com.microsoft.windowsazure.services.core.storage.Constants; + +/** + * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access + * signature. + */ +public final class SharedAccessTablePolicy { + + /** + * Assigns shared access permissions using the specified permissions string. + * + * @param value + * A String that represents the shared access permissions. The string must contain one or + * more of the following values. Note they must be lowercase, and the order that they are specified must + * be in the order of "rwdl". + *

      + *
    • d: Delete access.
    • + *
    • l: List access.
    • + *
    • r: Read access.
    • + *
    • w: Write access.
    • + *
    + * + * @return A java.util.EnumSet object that contains {@link SharedAccessTablePermissions} values that + * represents the set of shared access permissions. + */ + public static EnumSet permissionsFromString(final String value) { + final char[] chars = value.toCharArray(); + final EnumSet retSet = EnumSet.noneOf(SharedAccessTablePermissions.class); + + for (final char c : chars) { + switch (c) { + case 'r': + retSet.add(SharedAccessTablePermissions.QUERY); + break; + case 'a': + retSet.add(SharedAccessTablePermissions.ADD); + break; + case 'u': + retSet.add(SharedAccessTablePermissions.UPDATE); + break; + case 'd': + retSet.add(SharedAccessTablePermissions.DELETE); + break; + default: + throw new IllegalArgumentException("value"); + } + } + + return retSet; + } + + /** + * Converts the permissions specified for the shared access policy to a string. + * + * @param permissions + * A {@link SharedAccessTablePermissions} object that represents the shared access permissions. + * + * @return A String that represents the shared access permissions in the "rwdl" format, which is + * described at {@link SharedAccessTablePermissions#permissionsFromString}. + */ + public static String permissionsToString(final EnumSet permissions) { + if (permissions == null) { + return Constants.EMPTY_STRING; + } + + // The service supports a fixed order => rwdl + final StringBuilder builder = new StringBuilder(); + + if (permissions.contains(SharedAccessTablePermissions.QUERY)) { + builder.append("r"); + } + + if (permissions.contains(SharedAccessTablePermissions.ADD)) { + builder.append("a"); + } + + if (permissions.contains(SharedAccessTablePermissions.UPDATE)) { + builder.append("u"); + } + + if (permissions.contains(SharedAccessTablePermissions.DELETE)) { + builder.append("d"); + } + + return builder.toString(); + } + + /** + * The permissions for a shared access signature associated with this shared access policy. + */ + private EnumSet permissions; + + /** + * The expiry time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessExpiryTime; + + /** + * The start time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessStartTime; + + /** + * Creates an instance of the SharedAccessTablePolicy class. + * */ + public SharedAccessTablePolicy() { + // Empty Default Ctor + } + + /** + * @return the permissions + */ + public EnumSet getPermissions() { + return this.permissions; + } + + /** + * @return the sharedAccessExpiryTime + */ + public Date getSharedAccessExpiryTime() { + return this.sharedAccessExpiryTime; + } + + /** + * @return the sharedAccessStartTime + */ + public Date getSharedAccessStartTime() { + return this.sharedAccessStartTime; + } + + /** + * @param permissions + * the permissions to set + */ + public void setPermissions(final EnumSet permissions) { + this.permissions = permissions; + } + + /** + * @param sharedAccessExpiryTime + * the sharedAccessExpiryTime to set + */ + public void setSharedAccessExpiryTime(final Date sharedAccessExpiryTime) { + this.sharedAccessExpiryTime = sharedAccessExpiryTime; + } + + /** + * @param sharedAccessStartTime + * the sharedAccessStartTime to set + */ + public void setSharedAccessStartTime(final Date sharedAccessStartTime) { + this.sharedAccessStartTime = sharedAccessStartTime; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java new file mode 100644 index 000000000000..3333e2b3c54f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.windowsazure.services.table.client; + +import java.io.InputStream; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class TableAccessPolicyResponse extends AccessPolicyResponseBase { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public TableAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessTablePolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessTablePolicy retPolicy = new SharedAccessTablePolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessTablePolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java index 596e519612c3..162edd2bdab2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java @@ -391,7 +391,7 @@ public ArrayList execute(final CloudTableClient client, final Table final String batchID = String.format("batch_%s", UUID.randomUUID().toString()); final String changeSet = String.format("changeset_%s", UUID.randomUUID().toString()); - final HttpURLConnection request = TableRequest.batch(client.getEndpoint(), + final HttpURLConnection request = TableRequest.batch(client.getTransformedEndPoint(opContext), options.getTimeoutIntervalInMs(), batchID, null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java index 8b8ec3e422bc..3bffd84a306b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java @@ -256,7 +256,7 @@ 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.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.delete(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); @@ -323,7 +323,7 @@ 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.getEndpoint(), tableName, + 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); @@ -410,7 +410,7 @@ 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.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.merge(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); @@ -476,7 +476,7 @@ 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.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.update(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java new file mode 100644 index 000000000000..4ba7bd8bdfa1 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java @@ -0,0 +1,55 @@ +/** + * Copyright 2011 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.windowsazure.services.table.client; + +import java.util.HashMap; + +/** + * Represents the permissions for a container. + */ + +public final class TablePermissions { + + /** + * Gets the set of shared access policies for the table. + */ + private HashMap sharedAccessPolicies; + + /** + * Creates an instance of the TablePermissions class. + */ + public TablePermissions() { + this.sharedAccessPolicies = new HashMap(); + } + + /** + * Returns the set of shared access policies for the table. + * + * @return A HashMap object of {@link SharedAccessTablePolicy} objects that represent the set of shared + * access policies for the table. + */ + public HashMap getSharedAccessPolicies() { + return this.sharedAccessPolicies; + } + + /** + * @param sharedAccessPolicies + * the sharedAccessPolicies to set + */ + public void setSharedAccessPolicies(final HashMap sharedAccessPolicies) { + this.sharedAccessPolicies = sharedAccessPolicies; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java index 4642bec1b298..782aa98ef84e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java @@ -16,9 +16,16 @@ package com.microsoft.windowsazure.services.table.client; import java.io.IOException; +import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -423,6 +430,152 @@ protected static HttpURLConnection update(final URI rootUri, final String tableN return retConnection; } + /** + * Sets the ACL for the table. , Sign with length of aclBytes. + * + * @param rootUri + * A java.net.URI containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param options + * A {@link TableRequestOptions} 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 CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify null to + * safely ignore operation context. + * + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * */ + public static HttpURLConnection setAcl(final URI rootUri, final int timeoutInMs, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + + UriQueryBuilder queryBuilder = new UriQueryBuilder(); + queryBuilder.add("comp", "acl"); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, timeoutInMs, queryBuilder, + opContext); + retConnection.setRequestMethod("PUT"); + retConnection.setDoOutput(true); + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + TableConstants.HeaderConstants.ATOMPUB_TYPE); + + return retConnection; + } + + /** + * Writes a collection of shared access policies to the specified stream in XML format. + * + * @param sharedAccessPolicies + * A collection of shared access policies + * @param outWriter + * an sink to write the output to. + * @throws XMLStreamException + */ + public static void writeSharedAccessIdentifiersToStream( + final HashMap sharedAccessPolicies, final StringWriter outWriter) + throws XMLStreamException { + Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); + Utility.assertNotNull("outWriter", outWriter); + + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); + + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + final String errorMessage = String + .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container.", + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + + throw new IllegalArgumentException(errorMessage); + } + + // default is UTF8 + xmlw.writeStartDocument(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); + + for (final Entry entry : sharedAccessPolicies.entrySet()) { + final SharedAccessTablePolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); + + // Set the identifier + xmlw.writeStartElement(Constants.ID); + xmlw.writeCharacters(entry.getKey()); + xmlw.writeEndElement(); + + xmlw.writeStartElement(Constants.ACCESS_POLICY); + + // Set the Start Time + xmlw.writeStartElement(Constants.START); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); + // end Start + xmlw.writeEndElement(); + + // Set the Expiry Time + xmlw.writeStartElement(Constants.EXPIRY); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); + // end Expiry + xmlw.writeEndElement(); + + // Set the Permissions + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessTablePolicy.permissionsToString(policy.getPermissions())); + // end Permission + xmlw.writeEndElement(); + + // end AccessPolicy + xmlw.writeEndElement(); + // end SignedIdentifier + xmlw.writeEndElement(); + } + + // end SignedIdentifiers + xmlw.writeEndElement(); + // end doc + xmlw.writeEndDocument(); + } + + /** + * Constructs a web request to return the ACL for this table. Sign with no length specified. + * + * @param rootUri + * A java.net.URI containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param options + * A {@link TableRequestOptions} 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 CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify null to + * safely ignore operation context. + * + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getAcl(final URI rootUri, final String tableName, final int timeoutInMs, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + + UriQueryBuilder queryBuilder = new UriQueryBuilder(); + queryBuilder.add("comp", "acl"); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, timeoutInMs, queryBuilder, + opContext); + retConnection.setRequestMethod("GET"); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, TableConstants.HeaderConstants.ACCEPT_TYPE); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, "UTF8"); + retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION, + TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE); + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + TableConstants.HeaderConstants.ATOMPUB_TYPE); + + return retConnection; + } + /** * Private Default Constructor. */ diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java new file mode 100644 index 000000000000..bd0671e3ece1 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java @@ -0,0 +1,73 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.client; + +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Blob Test Base + * Blob test refactoring will be done in future. + */ +public class BlobTestBase { + public static boolean USE_DEV_FABRIC = false; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + + protected static CloudStorageAccount httpAcc; + protected static CloudBlobClient bClient; + protected static String testSuiteContainerName = generateRandomContainerName(); + protected static byte[] testData = new byte[] { 1, 2, 3, 4 }; + + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + + // UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); + + if (USE_DEV_FABRIC) { + httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); + } + else { + httpAcc = CloudStorageAccount.parse(CLOUD_ACCOUNT_HTTP); + } + + bClient = httpAcc.createCloudBlobClient(); + testSuiteContainerName = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(testSuiteContainerName); + container.create(); + } + + @AfterClass + public static void teardown() throws StorageException, URISyntaxException { + CloudBlobContainer container = bClient.getContainerReference(testSuiteContainerName); + container.delete(); + } + + protected static String generateRandomContainerName() { + String containerName = "container" + UUID.randomUUID().toString(); + return containerName.replace("-", ""); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java new file mode 100644 index 000000000000..c9da0ea84377 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java @@ -0,0 +1,501 @@ +/** + * Copyright 2011 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.windowsazure.services.blob.client; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Random; +import java.util.TimeZone; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Client Tests + */ +public class CloudBlobContainerTests extends BlobTestBase { + /** + * test SharedAccess of container. + * + * @XSCLCaseName ContainerInfoSharedAccess + */ + @Test + public void testContainerSaS() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, IOException { + + String name = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(name); + container.create(); + + CloudBlockBlob blob = container.getBlockBlobReference("test"); + blob.upload(new ByteArrayInputStream(new byte[100]), 100); + + SharedAccessBlobPolicy sp1 = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE, + SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 300); + SharedAccessBlobPolicy sp2 = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 300); + BlobContainerPermissions perms = new BlobContainerPermissions(); + + perms.getSharedAccessPolicies().put("full", sp1); + perms.getSharedAccessPolicies().put("readlist", sp2); + container.uploadPermissions(perms); + + String containerReadListSas = container.generateSharedAccessSignature(sp2, null); + CloudBlobContainer readListContainer = bClient.getContainerReference(container.getUri().toString() + "?" + + containerReadListSas); + Assert.assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), readListContainer + .getServiceClient().getCredentials().getClass().toString()); + + CloudBlockBlob blobFromSasContainer = readListContainer.getBlockBlobReference("test"); + blobFromSasContainer.download(new ByteArrayOutputStream()); + container.deleteIfExists(); + } + + @Test + public void testBlobSaS() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, IOException { + + String name = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(name); + container.create(); + + CloudBlockBlob blob = container.getBlockBlobReference("test"); + blob.upload(new ByteArrayInputStream(new byte[100]), 100); + + SharedAccessBlobPolicy sp = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 300); + BlobContainerPermissions perms = new BlobContainerPermissions(); + + perms.getSharedAccessPolicies().put("readperm", sp); + container.uploadPermissions(perms); + + CloudBlockBlob sasBlob = new CloudBlockBlob(new URI(blob.getUri().toString() + "?" + + blob.generateSharedAccessSignature(null, "readperm"))); + sasBlob.download(new ByteArrayOutputStream()); + container.deleteIfExists(); + } + + public final static SharedAccessBlobPolicy createSharedAccessPolicy(EnumSet sap, + int expireTimeInSeconds) { + + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + cal.setTime(new Date()); + cal.add(Calendar.SECOND, expireTimeInSeconds); + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); + policy.setPermissions(sap); + policy.setSharedAccessExpiryTime(cal.getTime()); + return policy; + + } + + @Test + public void testContainerGetSetPermission() throws StorageException, URISyntaxException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + + BlobContainerPermissions expectedPermissions; + BlobContainerPermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new BlobContainerPermissions(); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + newContainer.uploadPermissions(expectedPermissions); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessBlobPolicy policy1 = new SharedAccessBlobPolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.DELETE, + SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + newContainer.uploadPermissions(expectedPermissions); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + static void assertTablePermissionsEqual(BlobContainerPermissions expected, BlobContainerPermissions actual) { + HashMap expectedPolicies = expected.getSharedAccessPolicies(); + HashMap actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessBlobPolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessBlobPolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testContainerAcquireLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer leaseContainer1 = bClient.getContainerReference(name); + leaseContainer1.create(); + String proposedLeaseId1 = UUID.randomUUID().toString(); + + name = "leased" + generateRandomContainerName(); + CloudBlobContainer leaseContainer2 = bClient.getContainerReference(name); + leaseContainer2.create(); + String proposedLeaseId2 = UUID.randomUUID().toString(); + + try { + // 15 sec + + OperationContext operationContext1 = new OperationContext(); + leaseContainer1.acquireLease(15, proposedLeaseId1, null /*access condition*/, + null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + //infinite + String leaseId1; + String leaseId2; + OperationContext operationContext2 = new OperationContext(); + leaseId1 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2, + null /*access condition*/, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + leaseId2 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2); + Assert.assertEquals(leaseId1, leaseId2); + + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId1); + leaseContainer1.releaseLease(condition); + leaseContainer1.deleteIfExists(); + + condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId2); + leaseContainer2.releaseLease(condition); + leaseContainer2.deleteIfExists(); + } + } + + @Test + public void testContainerReleaseLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + + try { + // 15 sec + String proposedLeaseId = UUID.randomUUID().toString(); + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.releaseLease(condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.releaseLease(condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + @Test + public void testContainerBreakLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + String proposedLeaseId = UUID.randomUUID().toString(); + + try { + // 5 sec + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.breakLease(0, condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + Thread.sleep(15 * 1000); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.breakLease(0, condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId); + newContainer.releaseLease(condition); + newContainer.deleteIfExists(); + } + } + + @Test + public void testContainerRenewLeaseTest() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + String proposedLeaseId = UUID.randomUUID().toString(); + + try { + // 5 sec + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.renewLease(condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + newContainer.releaseLease(condition); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.renewLease(condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId); + newContainer.releaseLease(condition); + newContainer.deleteIfExists(); + } + } + + @Test + public void testBlobLeaseAcquireAndRelease() throws URISyntaxException, StorageException, IOException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer leasedContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = leasedContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + OperationContext operationContext = new OperationContext(); + final String leaseID = blobRef.acquireLease(15, null /*access condition*/, null /*proposed lease id */, + null/* BlobRequestOptions */, operationContext); + final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + // Try to upload without lease + try { + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + } + catch (final StorageException ex) { + Assert.assertEquals(ex.getHttpStatusCode(), 412); + Assert.assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.LEASE_ID_MISSING); + } + + // Try to upload with lease + blobRef.upload(new ByteArrayInputStream(buff), -1, leaseCondition, options, null); + + // Release lease + blobRef.releaseLease(leaseCondition); + + // now upload with no lease specified. + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + } + + @Test + public void testBlobLeaseBreak() throws URISyntaxException, StorageException, IOException, InterruptedException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer existingContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = existingContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + String leaseID = blobRef.acquireLease(null, null); + + OperationContext operationContext = new OperationContext(); + final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + blobRef.breakLease(0, leaseCondition, null/* BlobRequestOptions */, operationContext); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + } + + @Test + public void testBlobLeaseRenew() throws URISyntaxException, StorageException, IOException, InterruptedException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer existingContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = existingContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + final String leaseID = blobRef.acquireLease(15, null); + Thread.sleep(1000); + + AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + OperationContext operationContext = new OperationContext(); + blobRef.renewLease(leaseCondition, null/* BlobRequestOptions */, operationContext); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + + static String setLeasedState(CloudBlobContainer container, int leaseTime) throws StorageException { + String leaseId = UUID.randomUUID().toString(); + setUnleasedState(container); + return container.acquireLease(leaseTime, leaseId); + } + + static void setUnleasedState(CloudBlobContainer container) throws StorageException { + if (!container.createIfNotExist()) { + try { + container.breakLease(0); + } + catch (StorageException e) { + if (e.getHttpStatusCode() != HttpURLConnection.HTTP_BAD_REQUEST) { + throw e; + } + } + } + } + + @Test + public void testCopyFromBlob() throws StorageException, URISyntaxException, IOException, InterruptedException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + CloudBlob originalBlob = newContainer.getBlockBlobReference("newblob"); + originalBlob.upload(new ByteArrayInputStream(testData), testData.length); + + try { + CloudBlob copyBlob = newContainer.getBlockBlobReference(originalBlob.getName() + "copyed"); + copyBlob.copyFromBlob(originalBlob); + Thread.sleep(1000); + copyBlob.downloadAttributes(); + Assert.assertNotNull(copyBlob.copyState); + Assert.assertNotNull(copyBlob.copyState.getCopyId()); + Assert.assertNotNull(copyBlob.copyState.getCompletionTime()); + Assert.assertNotNull(copyBlob.copyState.getSource()); + Assert.assertFalse(copyBlob.copyState.getBytesCopied() == 0); + Assert.assertFalse(copyBlob.copyState.getTotalBytes() == 0); + for (final ListBlobItem blob : newContainer.listBlobs()) { + CloudBlob blobFromList = ((CloudBlob) blob); + blobFromList.downloadAttributes(); + } + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + @Test + public void testCopyFromBlobAbortTest() throws StorageException, URISyntaxException, IOException, + InterruptedException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + CloudBlob originalBlob = newContainer.getBlockBlobReference("newblob"); + byte[] data = new byte[16 * 1024 * 1024]; + Random r = new Random(); + r.nextBytes(data); + originalBlob.upload(new ByteArrayInputStream(data), testData.length); + + try { + CloudBlob copyBlob = newContainer.getBlockBlobReference(originalBlob.getName() + "copyed"); + copyBlob.copyFromBlob(originalBlob); + + try { + copyBlob.abortCopy(copyBlob.copyState.getCopyId()); + } + catch (StorageException e) { + if (!e.getErrorCode().contains("NoPendingCopyOperation")) { + throw e; + } + } + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java new file mode 100644 index 000000000000..a8405f5abd7f --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java @@ -0,0 +1,218 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageCredentials; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +public final class CloudQueueClientTests extends QueueTestBase { + + @Test + public void testQueueClientConstructor() throws URISyntaxException, StorageException { + StorageCredentials credentials = httpAcc.getCredentials(); + URI baseAddressUri = new URI(httpAcc.getQueueEndpoint().toString()); + + CloudQueueClient queueClient = new CloudQueueClient(baseAddressUri, credentials); + + Assert.assertEquals(baseAddressUri, queueClient.getEndpoint()); + Assert.assertEquals(credentials, queueClient.getCredentials()); + } + + @Test + public void testQueueClientConstructorInvalidParam() throws URISyntaxException, StorageException { + StorageCredentials credentials = httpAcc.getCredentials(); + try { + new CloudQueueClient(null, credentials); + Assert.fail(); + } + catch (IllegalArgumentException e) { + + } + + try { + char[] name = new char[2000]; + new CloudQueueClient(new URI(name.toString()), credentials); + Assert.fail(); + } + catch (URISyntaxException e) { + + } + } + + @Test + public void testListQueuesSmallNumber() throws URISyntaxException, StorageException { + int initialCount = 0; + + for (CloudQueue queue : qClient.listQueues()) { + initialCount++; + } + + HashMap metadata1 = new HashMap(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + + for (int i = 0; i < 25; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), UUID.randomUUID().toString() + .toLowerCase()), qClient); + q.setMetadata(metadata1); + q.create(); + } + + int count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + + Assert.assertEquals(count, initialCount + 25); + + String perfix = "prefixtest" + UUID.randomUUID().toString().substring(0, 8).toLowerCase(); + for (int i = 0; i < 25; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), perfix + + UUID.randomUUID().toString().toLowerCase()), qClient); + HashMap metadata2 = new HashMap(); + metadata2.put("tags", q.getName()); + q.setMetadata(metadata2); + q.create(); + } + + count = 0; + for (CloudQueue queue : qClient.listQueues(perfix, QueueListingDetails.METADATA, null, null)) { + count++; + Assert.assertTrue(queue.getMetadata().size() == 1 + && queue.getMetadata().get("tags").equals(queue.getName())); + } + + Assert.assertEquals(count, 25); + } + + @Test + public void testListQueuesAndListQueuesSegmentedLargeNumber() throws URISyntaxException, StorageException { + int count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + + int totalLimit = 5005; + if (count < totalLimit) { + + NumberFormat myFormat = NumberFormat.getInstance(); + myFormat.setMinimumIntegerDigits(4); + + for (int i = 0; i < totalLimit - count; i++) { + String sub = myFormat.format(i); + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), + String.format("listqueue" + sub.replace(",", ""))), qClient); + q.createIfNotExist(); + } + } + + count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + Assert.assertTrue(count >= totalLimit); + + ResultSegment segment = qClient.listQueuesSegmented(); + Assert.assertTrue(segment.getLength() == 5000); + Assert.assertTrue(segment.getContinuationToken() != null); + } + + @Test + public void testListQueuesSegmented() throws URISyntaxException, StorageException { + String perfix = "segment" + UUID.randomUUID().toString().substring(0, 8).toLowerCase(); + + HashMap metadata1 = new HashMap(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + + for (int i = 0; i < 35; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), perfix + + UUID.randomUUID().toString().toLowerCase()), qClient); + q.setMetadata(metadata1); + q.create(); + } + + ResultSegment segment1 = qClient.listQueuesSegmented(perfix); + Assert.assertTrue(segment1.getLength() == 35); + + ResultSegment segment2 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 5, null, + null, null); + Assert.assertTrue(segment2.getLength() == 5); + + int totalRoundTrip = 1; + while (segment2.getHasMoreResults()) { + segment2 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 5, + segment2.getContinuationToken(), null, null); + Assert.assertTrue(segment2.getLength() == 5); + totalRoundTrip++; + } + + Assert.assertTrue(totalRoundTrip == 7); + + ResultSegment segment3 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 0, null, + null, null); + Assert.assertTrue(segment3.getLength() == 35); + } + + @Test + public void testListQueuesEqual() throws URISyntaxException, StorageException { + int count1 = 0; + for (CloudQueue queue : qClient.listQueues()) { + count1++; + } + + int count2 = 0; + for (CloudQueue queue : qClient.listQueues("")) { + count2++; + } + + int count3 = 0; + for (CloudQueue queue : qClient.listQueues(null)) { + count3++; + } + + Assert.assertEquals(count1, count2); + Assert.assertEquals(count1, count3); + } + + @Test + public void testTimeout() throws URISyntaxException, StorageException { + Assert.assertTrue(qClient.getTimeoutInMs() == 30 * 1000); + qClient.setTimeoutInMs(60 * 1000); + Assert.assertTrue(qClient.getTimeoutInMs() == 60 * 1000); + } + + static String AppendQueueName(URI baseURI, String queueName) throws URISyntaxException { + if (baseURI == null) + return queueName; + + String baseAddress = baseURI.toString(); + if (baseAddress.endsWith("/")) { + return baseAddress + queueName; + } + else { + return baseAddress + "/" + queueName; + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java new file mode 100644 index 000000000000..156474bf037f --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java @@ -0,0 +1,1273 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Random; +import java.util.UUID; + +import javax.xml.stream.XMLStreamException; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Client Tests + */ +public class CloudQueueTests extends QueueTestBase { + @Test + public void queueGetSetPermissionTest() throws StorageException, URISyntaxException { + String name = generateRandomQueueName(); + CloudQueue newQueue = qClient.getQueueReference(name); + newQueue.create(); + + QueuePermissions expectedPermissions; + QueuePermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new QueuePermissions(); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + newQueue.uploadPermissions(expectedPermissions); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ, + SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD, + SharedAccessQueuePermissions.UPDATE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + newQueue.uploadPermissions(expectedPermissions); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + newQueue.deleteIfExists(); + } + } + + @Test + public void testQueueSAS() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomQueueName(); + CloudQueue newQueue = qClient.getQueueReference(name); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("sas queue test")); + + QueuePermissions expectedPermissions; + + try { + expectedPermissions = new QueuePermissions(); + // Add a policy, check setting and getting. + SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + String identifier = UUID.randomUUID().toString(); + + policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ, + SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD, + SharedAccessQueuePermissions.UPDATE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + newQueue.uploadPermissions(expectedPermissions); + + CloudQueueClient queueClientFromIdentifierSAS = getQueueClientForSas(newQueue, null, identifier); + CloudQueue identifierSasQueue = queueClientFromIdentifierSAS.getQueueReference(newQueue.getName()); + + identifierSasQueue.downloadAttributes(); + identifierSasQueue.exists(); + + identifierSasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = identifierSasQueue.retrieveMessage(); + identifierSasQueue.deleteMessage(message1); + + CloudQueueClient queueClientFromPolicySAS = getQueueClientForSas(newQueue, policy1, null); + CloudQueue policySasQueue = queueClientFromPolicySAS.getQueueReference(newQueue.getName()); + policySasQueue.exists(); + policySasQueue.downloadAttributes(); + + policySasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message2 = policySasQueue.retrieveMessage(); + policySasQueue.deleteMessage(message2); + } + finally { + // cleanup + newQueue.deleteIfExists(); + } + } + + private CloudQueueClient getQueueClientForSas(CloudQueue queue, SharedAccessQueuePolicy policy, + String accessIdentifier) throws InvalidKeyException, StorageException { + String sasString = queue.generateSharedAccessSignature(policy, accessIdentifier); + return new CloudQueueClient(qClient.getEndpoint(), new StorageCredentialsSharedAccessSignature(sasString)); + } + + static void assertQueuePermissionsEqual(QueuePermissions expected, QueuePermissions actual) { + HashMap expectedPolicies = expected.getSharedAccessPolicies(); + HashMap actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessQueuePolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessQueuePolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testQueueClientConstructor() throws URISyntaxException, StorageException { + String queueName = "queue"; + String baseAddress = AppendQueueName(qClient.getEndpoint(), queueName); + CloudQueue queue1 = new CloudQueue(baseAddress, qClient); + Assert.assertEquals(queueName, queue1.getName()); + Assert.assertTrue(queue1.getUri().toString().endsWith(baseAddress)); + Assert.assertEquals(qClient, queue1.getServiceClient()); + + CloudQueue queue2 = new CloudQueue(new URI(AppendQueueName(qClient.getEndpoint(), queueName)), qClient); + + Assert.assertEquals(queueName, queue2.getName()); + Assert.assertEquals(qClient, queue2.getServiceClient()); + + CloudQueue queue3 = new CloudQueue(queueName, qClient); + Assert.assertEquals(queueName, queue3.getName()); + Assert.assertEquals(qClient, queue3.getServiceClient()); + } + + @Test + public void testGetMetadata() throws URISyntaxException, StorageException { + HashMap metadata = new HashMap(); + metadata.put("ExistingMetadata", "ExistingMetadataValue"); + queue.setMetadata(metadata); + queue.uploadMetadata(); + queue.downloadAttributes(); + Assert.assertEquals(queue.getMetadata().get("ExistingMetadata"), "ExistingMetadataValue"); + Assert.assertTrue(queue.getMetadata().containsKey("ExistingMetadata")); + + HashMap empytMetadata = null; + queue.setMetadata(empytMetadata); + queue.uploadMetadata(); + queue.downloadAttributes(); + Assert.assertTrue(queue.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadata() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap metadata1 = new HashMap(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey("ExistingMetadata1")); + + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().containsKey("ExistingMetadata1")); + } + + @Test + public void testUploadMetadataNullInput() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap metadata1 = new HashMap(); + String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); + metadata1.put(key, "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey(key)); + + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().containsKey(key)); + + queue.setMetadata(null); + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadataClearExisting() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap metadata1 = new HashMap(); + String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); + metadata1.put(key, "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey(key)); + + HashMap metadata2 = new HashMap(); + queue.setMetadata(metadata2); + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadataNotFound() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.uploadMetadata(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueCreate() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + try { + HashMap metadata1 = new HashMap(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + queue.create(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT); + + } + + queue.downloadAttributes(); + OperationContext createQueueContext2 = new OperationContext(); + queue.create(null, createQueueContext2); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + queue.delete(); + } + + @Test + public void testQueueCreateAlreadyExists() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + queue.create(null, createQueueContext1); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext createQueueContext2 = new OperationContext(); + queue.create(null, createQueueContext2); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void testQueueCreateAfterDelete() throws URISyntaxException, StorageException { + + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + try { + queue.create(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testQueueCreateIfNotExists() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext)); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + } + + @Test + public void testQueueCreateIfNotExistsAfterCreate() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext createQueueContext2 = new OperationContext(); + Assert.assertFalse(queue.createIfNotExist(null, createQueueContext2)); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void testQueueCreateIfNotExistsAfterDelete() throws URISyntaxException, StorageException { + + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + try { + queue.createIfNotExist(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testQueueDelete() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext deleteQueueContext = new OperationContext(); + queue.delete(null, deleteQueueContext); + Assert.assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + try { + queue.downloadAttributes(); + Assert.fail(); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + + queue.delete(); + } + + @Test + public void testDeleteQueueIfExists() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + Assert.assertFalse(queue.deleteIfExists()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + + try { + queue.create(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testDeleteNonExistingQueue() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + final OperationContext existQueueContext1 = new OperationContext(); + Assert.assertTrue(!queue.exists(null, existQueueContext1)); + Assert.assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + + try { + queue.delete(); + Assert.fail("Queue delete no exsiting queue. "); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + } + + @Test + public void testQueueExist() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + final OperationContext existQueueContext1 = new OperationContext(); + Assert.assertTrue(!queue.exists(null, existQueueContext1)); + Assert.assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final OperationContext existQueueContext2 = new OperationContext(); + Assert.assertTrue(queue.exists(null, existQueueContext2)); + Assert.assertEquals(existQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_OK); + } + + @Test + public void testClearMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + queue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue.addMessage(message2); + + int count = 0; + for (CloudQueueMessage m : queue.peekMessages(32)) { + count++; + } + + Assert.assertTrue(count == 2); + + OperationContext oc = new OperationContext(); + queue.clear(null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + count = 0; + for (CloudQueueMessage m : queue.peekMessages(32)) { + count++; + } + + Assert.assertTrue(count == 0); + } + + public void testClearMessagesEmptyQueue() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + queue.create(); + queue.clear(); + queue.delete(); + } + + public void testClearMessagesNotFound() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + try { + queue.clear(); + Assert.fail(); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + } + + @Test + public void testAddMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = UUID.randomUUID().toString(); + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(message.getMessageContentAsString(), msgContent); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + + queue.delete(); + } + + @Test + public void testAddMessageToNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + + String messageContent = "messagetest"; + CloudQueueMessage message1 = new CloudQueueMessage(messageContent); + + try { + newQueue.addMessage(message1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueUnicodeAndXmlMessageTest() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = "好"; + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(message.getMessageContentAsString(), msgContent); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + //Assert.assertEquals(message.getMessageContentAsByte(), msgFromRetrieve1.getMessageContentAsByte()); + + queue.delete(); + } + + @Test + public void testAddMessageLargeMessageInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final Random rand = new Random(); + + byte[] content = new byte[64 * 1024]; + rand.nextBytes(content); + CloudQueueMessage message1 = new CloudQueueMessage(new String(content)); + + try { + queue.addMessage(message1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testAddMessageWithVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage m1 = queue.retrieveMessage(); + Date d1 = m1.getExpirationTime(); + queue.deleteMessage(m1); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage m2 = queue.retrieveMessage(); + Date d2 = m2.getExpirationTime(); + queue.deleteMessage(m2); + Assert.assertTrue(d1.before(d2)); + } + + @Test + public void testAddMessageNullMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + try { + queue.addMessage(null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testAddMessageSpecialVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + CloudQueueMessage message = new CloudQueueMessage("test"); + queue.addMessage(message, 1, 0, null, null); + queue.addMessage(message, 7 * 24 * 60 * 60, 0, null, null); + queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60 - 1, null, null); + + try { + queue.addMessage(message, -1, 0, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 0, -1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 7 * 24 * 60 * 60 + 1, 0, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 0, 7 * 24 * 60 * 60 + 1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.CONTENT), null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testDeleteMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage message : newQueue.retrieveMessages(32)) { + OperationContext deleteQueueContext = new OperationContext(); + newQueue.deleteMessage(message, null, deleteQueueContext); + Assert.assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + Assert.assertTrue(newQueue.retrieveMessage() == null); + } + + @Test + public void testQueueCreateAddingMetaData() throws URISyntaxException, StorageException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + + final HashMap metadata = new HashMap(5); + for (int i = 0; i < 5; i++) { + metadata.put("key" + i, "value" + i); + } + + queue.setMetadata(metadata); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + } + + @Test + public void testDeleteMessageNullMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + try { + queue.deleteMessage(null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(); + Date expirationTime1 = message1.getExpirationTime(); + Date insertionTime1 = message1.getInsertionTime(); + Date nextVisibleTime1 = message1.getNextVisibleTime(); + newQueue.deleteMessage(message1); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message2 = newQueue.retrieveMessage(); + Date expirationTime2 = message2.getExpirationTime(); + Date insertionTime2 = message2.getInsertionTime(); + Date nextVisibleTime2 = message2.getNextVisibleTime(); + newQueue.deleteMessage(message2); + Assert.assertTrue(expirationTime1.before(expirationTime2)); + Assert.assertTrue(insertionTime1.before(insertionTime2)); + Assert.assertTrue(nextVisibleTime1.before(nextVisibleTime2)); + } + + @Test + public void testRetrieveMessageNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.retrieveMessage(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testRetrieveMessageInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + try { + queue.retrieveMessage(-1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessage(7 * 24 * 3600 + 1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessagesFromEmptyQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + for (CloudQueueMessage m : newQueue.retrieveMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + } + + @Test + public void testRetrieveMessagesNonFound() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.retrieveMessages(1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testDequeueCountIncreases() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(1, null, null); + Assert.assertTrue(message1.getDequeueCount() == 1); + + for (int i = 2; i < 5; i++) { + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + CloudQueueMessage message2 = newQueue.retrieveMessage(1, null, null); + Assert.assertTrue(message2.getDequeueCount() == i); + } + + } + + @Test + public void testRetrieveMessageSpecialVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + + try { + queue.retrieveMessage(-1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage m : newQueue.retrieveMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() != null); + } + } + + @Test + public void testRetrieveMessagesInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.createIfNotExist(); + + for (int i = 0; i < 33; i++) { + queue.addMessage(new CloudQueueMessage("test" + i)); + } + + queue.retrieveMessages(1, 1, null, null); + queue.retrieveMessages(32, 1, null, null); + + try { + queue.retrieveMessages(-1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessages(0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessages(33); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testPeekMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage msg = newQueue.peekMessage(); + Assert.assertTrue(msg.getId() != null); + Assert.assertTrue(msg.getPopReceipt() == null); + + newQueue.delete(); + } + + @Test + public void testPeekMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage m : newQueue.peekMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + + newQueue.delete(); + } + + @Test + public void testPeekMessagesInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.createIfNotExist(); + + for (int i = 0; i < 33; i++) { + queue.addMessage(new CloudQueueMessage("test" + i)); + } + + queue.peekMessages(1); + queue.peekMessages(32); + + try { + queue.peekMessages(-1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.peekMessages(0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.peekMessages(33); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testPeekMessageNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.peekMessage(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testPeekMessagesNonFound() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.peekMessages(1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testPeekMessagesFromEmptyQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + for (CloudQueueMessage m : newQueue.peekMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + } + + @Test + public void testUpdateMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + + String messageContent = "messagetest"; + CloudQueueMessage message1 = new CloudQueueMessage(messageContent); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage(messageContent); + queue.addMessage(message2); + + String newMesage = message1.getMessageContentAsString() + "updated"; + + for (CloudQueueMessage message : queue.retrieveMessages(32)) { + OperationContext oc = new OperationContext(); + message.setMessageContent(newMesage); + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + CloudQueueMessage messageFromGet = queue.retrieveMessage(); + Assert.assertEquals(messageFromGet.getMessageContentAsString(), messageContent); + } + } + + @Test + public void testUpdateMessageFullPass() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + CloudQueueMessage message = new CloudQueueMessage("message"); + newQueue.addMessage(message, 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(); + String popreceipt1 = message1.getPopReceipt(); + Date NextVisibleTim1 = message1.getNextVisibleTime(); + newQueue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null); + String popreceipt2 = message1.getPopReceipt(); + Date NextVisibleTim2 = message1.getNextVisibleTime(); + Assert.assertTrue(popreceipt2 != popreceipt1); + Assert.assertTrue(NextVisibleTim1.before(NextVisibleTim2)); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String newMesage = message.getMessageContentAsString() + "updated"; + message.setMessageContent(newMesage); + OperationContext oc = new OperationContext(); + newQueue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + String popreceipt3 = message1.getPopReceipt(); + Date NextVisibleTim3 = message1.getNextVisibleTime(); + Assert.assertTrue(popreceipt3 != popreceipt2); + Assert.assertTrue(NextVisibleTim2.before(NextVisibleTim3)); + + Assert.assertTrue(newQueue.retrieveMessage() == null); + + newQueue.updateMessage(message1, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null); + + CloudQueueMessage messageFromGet = newQueue.retrieveMessage(); + Assert.assertEquals(messageFromGet.getMessageContentAsString(), message1.getMessageContentAsString()); + } + + @Test + public void testUpdateMessageWithContentChange() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue.addMessage(message2); + + for (CloudQueueMessage message : queue.retrieveMessages(32)) { + OperationContext oc = new OperationContext(); + message.setMessageContent(message.getMessageContentAsString() + "updated"); + queue.updateMessage(message, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + } + + @Test + public void testUpdateMessageNullMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + try { + queue.updateMessage(null, 0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testUpdateMessageInvalidMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(null, null); + + CloudQueueMessage message = new CloudQueueMessage("test"); + queue.addMessage(message, 1, 0, null, null); + + try { + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.CONTENT), null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testGetApproximateMessageCount() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + Assert.assertTrue(queue.getApproximateMessageCount() == 0); + queue.addMessage(new CloudQueueMessage("message1")); + queue.addMessage(new CloudQueueMessage("message2")); + Assert.assertTrue(queue.getApproximateMessageCount() == 0); + queue.downloadAttributes(); + Assert.assertTrue(queue.getApproximateMessageCount() == 2); + queue.delete(); + } + + @Test + public void testShouldEncodeMessage() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = UUID.randomUUID().toString(); + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.setShouldEncodeMessage(true); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + queue.deleteMessage(msgFromRetrieve1); + + queue.setShouldEncodeMessage(false); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve2 = queue.retrieveMessage(); + Assert.assertEquals(msgFromRetrieve2.getMessageContentAsString(), msgContent); + queue.deleteMessage(msgFromRetrieve2); + + queue.setShouldEncodeMessage(true); + } + + @Test + public void testQueueDownloadAttributes() throws URISyntaxException, StorageException, + UnsupportedEncodingException, XMLStreamException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + + final CloudQueue queue1 = qClient.getQueueReference(queueName); + queue1.create(); + + final CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue1.addMessage(message1); + + final CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue1.addMessage(message2); + + final HashMap metadata = new HashMap(5); + int sum = 5; + for (int i = 0; i < sum; i++) { + metadata.put("key" + i, "value" + i); + } + + queue1.setMetadata(metadata); + queue1.uploadMetadata(); + + final CloudQueue queue2 = qClient.getQueueReference(queueName); + queue2.downloadAttributes(); + + System.out.println(queue2.getApproximateMessageCount()); + + int count = 0; + for (final String s : queue2.getMetadata().keySet()) { + count++; + System.out.println(s + ":" + queue2.getMetadata().get(s)); + } + + Assert.assertEquals(count, sum); + + queue1.delete(); + } + + @Test + public void testQueueDownloadAttributesNotFound() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.downloadAttributes(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueUpdateMetaData() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final HashMap metadata = new HashMap(5); + for (int i = 0; i < 5; i++) { + metadata.put("key" + i, "value" + i); + } + + queue.setMetadata(metadata); + queue.uploadMetadata(); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java new file mode 100644 index 000000000000..feb5b1ceb4a1 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java @@ -0,0 +1,87 @@ +/** + * Copyright 2011 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.windowsazure.services.queue.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Queue Test Base + * Queue test refactoring will be done in future. + */ +public class QueueTestBase { + public static boolean USE_DEV_FABRIC = false; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + + protected static CloudStorageAccount httpAcc; + protected static CloudQueueClient qClient; + protected static String testSuiteQueueName = generateRandomQueueName(); + protected static CloudQueue queue; + + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + + // UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); + + if (USE_DEV_FABRIC) { + httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); + } + else { + httpAcc = CloudStorageAccount.parse(CLOUD_ACCOUNT_HTTP); + } + + qClient = httpAcc.createCloudQueueClient(); + testSuiteQueueName = generateRandomQueueName(); + queue = qClient.getQueueReference(testSuiteQueueName); + queue.create(); + } + + @AfterClass + public static void teardown() throws StorageException, URISyntaxException { + CloudQueue queue = qClient.getQueueReference(testSuiteQueueName); + queue.delete(); + } + + protected static String generateRandomQueueName() { + String queueName = "queue" + UUID.randomUUID().toString(); + return queueName.replace("-", ""); + } + + static String AppendQueueName(URI baseURI, String queueName) throws URISyntaxException { + if (baseURI == null) + return queueName; + + String baseAddress = baseURI.toString(); + if (baseAddress.endsWith("/")) { + return baseAddress + queueName; + } + else { + return baseAddress + "/" + queueName; + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java index 8060002ca8ae..86c5056cf9f7 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java @@ -17,15 +17,24 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.UUID; import junit.framework.Assert; import org.junit.Test; import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.windowsazure.services.core.storage.StorageException; /** @@ -35,10 +44,12 @@ public class TableClientTests extends TableTestBase { @Test public void listTablesSegmented() throws IOException, URISyntaxException, StorageException { String tableBaseName = generateRandomTableName(); + ArrayList tables = new ArrayList(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -72,7 +83,8 @@ public void listTablesSegmented() throws IOException, URISyntaxException, Storag } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @@ -83,7 +95,8 @@ public void listTablesSegmentedNoPrefix() throws IOException, URISyntaxException ArrayList tables = new ArrayList(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -124,7 +137,8 @@ public void listTablesSegmentedNoPrefix() throws IOException, URISyntaxException } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @@ -135,7 +149,8 @@ public void listTablesWithIterator() throws IOException, URISyntaxException, Sto ArrayList tables = new ArrayList(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -180,21 +195,23 @@ public void listTablesWithIterator() throws IOException, URISyntaxException, Sto } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @Test - public void tableCreateAndAttemptCreateOnceExists() throws StorageException { + public void tableCreateAndAttemptCreateOnceExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); + table.create(); + Assert.assertTrue(table.exists()); // Should fail as it already exists try { - tClient.createTable(tableName); + table.create(); fail(); } catch (StorageException ex) { @@ -203,84 +220,419 @@ public void tableCreateAndAttemptCreateOnceExists() throws StorageException { } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableCreateExistsAndDelete() throws StorageException { + public void tableCreateExistsAndDelete() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.deleteTableIfExists(tableName)); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); + Assert.assertTrue(table.deleteIfExists()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableCreateIfNotExists() throws StorageException { + public void tableCreateIfNotExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertFalse(tClient.createTableIfNotExists(tableName)); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); + Assert.assertFalse(table.createIfNotExist()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableDeleteIfExists() throws StorageException { + public void tableDeleteIfExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); - Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + Assert.assertFalse(table.deleteIfExists()); - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.deleteTableIfExists(tableName)); - Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + table.create(); + Assert.assertTrue(table.exists()); + Assert.assertTrue(table.deleteIfExists()); + Assert.assertFalse(table.deleteIfExists()); } @Test - public void tableDeleteWhenExistAndNotExists() throws StorageException { + public void tableDeleteWhenExistAndNotExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + try { // Should fail as it doesnt already exists try { - tClient.deleteTable(tableName); + table.delete(); fail(); } catch (StorageException ex) { Assert.assertEquals(ex.getMessage(), "Not Found"); } - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); - tClient.deleteTable(tableName); - Assert.assertFalse(tClient.doesTableExist(tableName)); + table.create(); + Assert.assertTrue(table.exists()); + table.delete(); + Assert.assertFalse(table.exists()); } finally { - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableDoesTableExist() throws StorageException { + public void tableDoesTableExist() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + try { - Assert.assertFalse(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); + Assert.assertFalse(table.exists()); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } + + @Test + public void tableGetSetPermissionTest() throws StorageException, URISyntaxException { + String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + table.create(); + + TablePermissions expectedPermissions; + TablePermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new TablePermissions(); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + table.uploadPermissions(expectedPermissions); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + table.uploadPermissions(expectedPermissions); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + static void assertTablePermissionsEqual(TablePermissions expected, TablePermissions actual) { + HashMap expectedPolicies = expected.getSharedAccessPolicies(); + HashMap actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessTablePolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessTablePolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testTableSASFromIdentifier() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomTableName(); + CloudTable table = tClient.getTableReference(name); + table.create(); + + try { + TablePermissions expectedPermissions = new TablePermissions(); + String identifier = UUID.randomUUID().toString(); + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + table.uploadPermissions(expectedPermissions); + + // Insert 500 entities in Batches to query + for (int i = 0; i < 5; i++) { + TableBatchOperation batch = new TableBatchOperation(); + + for (int j = 0; j < 100; j++) { + class1 ent = generateRandomEnitity("javatables_batch_" + Integer.toString(i)); + ent.setRowKey(String.format("%06d", j)); + batch.insert(ent); + } + + tClient.execute(name, batch); + } + + CloudTableClient tableClientFromIdentifierSAS = getTableForSas(table, null, identifier, null, null, null, + null); + + { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery query = TableQuery.from(name, class1.class).where( + String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); + + int count = 0; + + for (class1 ent : tableClientFromIdentifierSAS.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + } + + { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tableClientFromIdentifierSAS.execute(name, + TableOperation.insertOrMerge(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tableClientFromIdentifierSAS.execute(name, TableOperation.insertOrMerge(secondEntity)); + + // Retrieve entity + TableResult queryResult = tableClientFromIdentifierSAS.execute(name, TableOperation.retrieve( + baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult. getResultAsType(); + + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + @Test + public void testTableSASFromPermission() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomTableName(); + CloudTable table = tClient.getTableReference(name); + table.create(); + + try { + TablePermissions expectedPermissions = new TablePermissions(); + String identifier = UUID.randomUUID().toString(); + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + table.uploadPermissions(expectedPermissions); + + // Insert 500 entities in Batches to query + for (int i = 0; i < 5; i++) { + TableBatchOperation batch = new TableBatchOperation(); + + for (int j = 0; j < 100; j++) { + class1 ent = generateRandomEnitity("javatables_batch_" + Integer.toString(i)); + ent.setRowKey(String.format("%06d", j)); + batch.insert(ent); + } + + tClient.execute(name, batch); + } + + CloudTableClient tableClientFromPermission = getTableForSas(table, policy1, null, "javatables_batch_0", + "0", "javatables_batch_9", "9"); + CloudTableClient tableClientFromPermissionJustPks = getTableForSas(table, policy1, null, + "javatables_batch_0", null, "javatables_batch_9", null); + + { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery query = TableQuery.from(name, class1.class).where( + String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); + + int count = 0; + + for (class1 ent : tableClientFromPermission.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + + count = 0; + + for (class1 ent : tableClientFromPermissionJustPks.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + } + + { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("javatables_batch_0" + UUID.randomUUID().toString()); + baseEntity.setRowKey("0" + UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tableClientFromPermission.execute(name, + TableOperation.insertOrMerge(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tableClientFromPermission.execute(name, TableOperation.insertOrMerge(secondEntity)); + + // Retrieve entity + TableResult queryResult = tableClientFromPermission.execute(name, TableOperation.retrieve( + baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult. getResultAsType(); + + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + private CloudTableClient getTableForSas(CloudTable table, SharedAccessTablePolicy policy, String accessIdentifier, + String startPk, String startRk, String endPk, String endRk) throws InvalidKeyException, StorageException { + String sasString = table + .generateSharedAccessSignature(policy, accessIdentifier, startPk, startRk, endPk, endRk); + return new CloudTableClient(tClient.getEndpoint(), new StorageCredentialsSharedAccessSignature(sasString)); + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java index b30f200fa61a..d235ed326cc6 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java @@ -35,7 +35,7 @@ */ public class TableTestBase { public static boolean USE_DEV_FABRIC = false; - public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=http;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; public static class class1 extends TableServiceEntity { @@ -568,11 +568,11 @@ public static class1 generateRandomEnitity(String pk) { @BeforeClass public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { - // UNCOMMENT TO USE FIDDLER - // System.setProperty("http.proxyHost", "localhost"); - // System.setProperty("http.proxyPort", "8888"); - // System.setProperty("https.proxyHost", "localhost"); - // System.setProperty("https.proxyPort", "8888"); + //UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); if (USE_DEV_FABRIC) { httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); } @@ -584,12 +584,14 @@ public static void setup() throws URISyntaxException, StorageException, InvalidK tClient = httpAcc.createCloudTableClient(); qClient = httpAcc.createCloudQueueClient(); testSuiteTableName = generateRandomTableName(); - tClient.createTable(testSuiteTableName); + CloudTable table = tClient.getTableReference(testSuiteTableName); + table.create(); } @AfterClass - public static void teardown() throws StorageException { - tClient.deleteTable(testSuiteTableName); + public static void teardown() throws StorageException, URISyntaxException { + CloudTable table = tClient.getTableReference(testSuiteTableName); + table.delete(); } protected static String generateRandomTableName() {