/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.microsoft.azure.storage.file;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
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.HashMap;

import com.microsoft.azure.storage.AccessCondition;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.IPRange;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.SharedAccessPolicyHandler;
import com.microsoft.azure.storage.SharedAccessPolicySerializer;
import com.microsoft.azure.storage.SharedAccessProtocols;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.RequestLocationMode;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SharedAccessSignatureHelper;
import com.microsoft.azure.storage.core.StorageCredentialsHelper;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;

/**
 * Represents a share in the Microsoft Azure File service.
 *

 * Shares hold directories, which are encapsulated as {@link CloudFileDirectory} objects, and directories hold files.
 * Directories can also contain sub-directories.
 */
public final class CloudFileShare {
    /**
     * Represents the share metadata.
     */
    private HashMap<String, String> metadata = new HashMap<String, String>();

    /**
     * Holds the share properties.
     */
    FileShareProperties properties = new FileShareProperties();

    /**
     * Holds the name of the share.
     */
    String name = null;

    /**
     * Holds the list of URIs for all locations.
     */
    private StorageUri storageUri;

    /**
     * Holds the snapshot ID.
     */
    String snapshotID;

    /**
     * Holds a reference to the associated service client.
     */
    private CloudFileClient fileServiceClient;

    /**
     * Creates an instance of the CloudFileShare class using the specified name and client.
     *
     * @param shareName
     *            A String which represents the name of the share, which must adhere to share
     *            naming rules.
     *            The share name should not include any path separator characters (/).
     *            Share names must be lowercase, between 3-63 characters long and must start with a letter or
     *            number. Share names may contain only letters, numbers, and the dash (-) character.
     * @param client
     *            A {@link CloudFileClient} object that represents the associated service client, and that specifies the
     *            endpoint for the File service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI constructed based on the shareName is invalid.
     *
     * @see Naming and Referencing Shares,
     *      Directories, Files, and Metadata
     */
    public CloudFileShare(final String shareName, String snapshotID, final CloudFileClient client) throws URISyntaxException,
            StorageException {
        Utility.assertNotNull("client", client);
        Utility.assertNotNull("shareName", shareName);

        this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), shareName);
        this.name = shareName;
        this.snapshotID = snapshotID;
        this.fileServiceClient = client;
    }

    /**
     * Creates an instance of the CloudFileShare class using the specified URI.
     *
     * @param uri
     *            A java.net.URI object that represents the absolute URI of the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudFileShare(final URI uri) throws StorageException {
        this(new StorageUri(uri));
    }

    /**
     * Creates an instance of the CloudFileShare class using the specified URI.
     *
     * @param storageUri
     *            A {@link StorageUri} object which represents the absolute URI of the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudFileShare(final StorageUri storageUri) throws StorageException {
        this(storageUri, (StorageCredentials) null);
    }

    /**
     * Creates an instance of the CloudFileShare class using the specified URI and credentials.
     *
     * @param uri
     *            A java.net.URI object that represents the absolute URI of the share.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @param snapshotID
     *            A String that represents the snapshot version, if applicable.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudFileShare(final URI uri, final StorageCredentials credentials) throws StorageException {
        this(new StorageUri(uri), credentials);
    }

    /**
     * Creates an instance of the CloudFileShare class using the specified StorageUri and credentials.
     *
     * @param storageUri
     *            A {@link StorageUri} object which represents the absolute StorageUri of the share.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudFileShare(final StorageUri storageUri, final StorageCredentials credentials) throws StorageException {
        this.parseQueryAndVerify(storageUri, credentials);
    }

    /**
     * Creates the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create() throws StorageException {
        this.create(null /* options */, null /* opContext */);
    }

    /**
     * Creates the share using the specified options and operation context.
     *
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create(FileRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoSnapshot();
        if (this.properties != null && this.properties.getShareQuota() != null) {
            Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA);
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this, createImpl(options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> createImpl(final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, Void> putRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, Void>(options, this.getStorageUri()) {
            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                final HttpURLConnection request = FileRequest.createShare(
                        share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, share.getProperties());
                return request;
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudFileShare share, OperationContext context) {
                FileRequest.addMetadata(connection, share.metadata, context);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
            }

            @Override
            public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                // Set attributes
                final FileShareAttributes attributes = FileResponse.getFileShareAttributes(this.getConnection(),
                        client.isUsePathStyleUris());

                // The response from the service does not include the share quota.
                // Instead, we keep the existing value.
                Integer oldShareQuota = share.properties.getShareQuota();
                share.properties = attributes.getProperties();
                share.properties.setShareQuota(oldShareQuota);
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Creates the share if it does not exist.
     *
     * @return true if the share did not already exist and was created; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean createIfNotExists() throws StorageException {
        return this.createIfNotExists(null /* options */, null /* opContext */);
    }

    /**
     * Creates the share if it does not exist, using the specified request options and operation context.
     *
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request.
     *            Specifying null will use the default request options from the associated service client
     *            ({@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return true if the share did not already exist and was created; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException {
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext);
        if (exists) {
            return false;
        }
        else {
            try {
                this.create(options, opContext);
                return true;
            }
            catch (StorageException e) {
                if (e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT
                        && StorageErrorCodeStrings.SHARE_ALREADY_EXISTS.equals(e.getErrorCode())) {
                    return false;
                }
                else {
                    throw e;
                }
            }
        }
    }

    /**
     * Deletes the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void delete() throws StorageException {
        this.delete(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Deletes the share using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        this.delete(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext);
    }

    /**
     * Deletes the share using the specified snapshot and request options, and operation context.
     *

     * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use
     * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value
     * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share.
     *
     * @param deleteSnapshotsOption
     *            A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share
     *            and its snapshots.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this, deleteImpl(deleteSnapshotsOption, accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> deleteImpl(
            final DeleteShareSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition, final FileRequestOptions options) {

        final StorageRequest<CloudFileClient, CloudFileShare, Void> putRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, Void>(options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(
                    CloudFileClient client, CloudFileShare share, OperationContext context) throws Exception {
                return FileRequest.deleteShare(
                        share.getTransformedAddress().getPrimaryUri(), options, context, accessCondition, share.snapshotID, deleteSnapshotsOption);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
            }

            @Override
            public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }
        };

        return putRequest;
    }

    /**
     * Deletes the share if it exists.
     *
     * @return true if the share existed and was deleted; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean deleteIfExists() throws StorageException {
        return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Deletes the share if it exists using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return true if the share existed and was deleted; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        return this.deleteIfExists(DeleteShareSnapshotsOption.NONE, accessCondition, options, opContext);

    }

    /**
     * Deletes the share if it exists, using the specified snapshot and request options, and operation context.
     *

     * A share that has snapshots cannot be deleted unless the snapshots are also deleted. If a share has snapshots, use
     * the {@link DeleteShareSnapshotsOption#INCLUDE_SNAPSHOTS} value
     * in the deleteSnapshotsOption parameter to include the snapshots when deleting the base share.
     *
     * @param deleteSnapshotsOption
     *            A {@link DeleteShareSnapshotsOption} object that indicates whether to delete only snapshots, or the share
     *            and its snapshots.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return true if the share existed and was deleted; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean deleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext);
        if (exists) {
            try {
                this.delete(deleteSnapshotsOption, accessCondition, options, opContext);
                return true;
            }
            catch (StorageException e) {
                if (e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
                        && StorageErrorCodeStrings.SHARE_NOT_FOUND.equals(e.getErrorCode())) {
                    return false;
                }
                else {
                    throw e;
                }
            }
        }
        else {
            return false;
        }
    }

    /**
     * Downloads the share's attributes, which consist of metadata and properties.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void downloadAttributes() throws StorageException {
        this.downloadAttributes(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads the share's attributes, which consist of metadata and properties, using the specified request
     * options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void downloadAttributes(AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> downloadAttributesImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, Void> getRequest = new StorageRequest<CloudFileClient, CloudFileShare, Void>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, share.snapshotID);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
            }

            @Override
            public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                // Set attributes
                final FileShareAttributes attributes = FileResponse.getFileShareAttributes(this.getConnection(),
                        client.isUsePathStyleUris());
                share.metadata = attributes.getMetadata();
                share.properties = attributes.getProperties();
                return null;
            }
        };

        return getRequest;
    }

    /**
     * Downloads the permission settings for the share.
     *
     * @return A {@link FileSharePermissions} object that represents the share's permissions.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public FileSharePermissions downloadPermissions() throws StorageException {
        return this.downloadPermissions(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads the permissions settings for the share using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return A {@link FileSharePermissions} object that represents the share's permissions.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public FileSharePermissions downloadPermissions(AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoSnapshot();

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        return ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                downloadPermissionsImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, FileSharePermissions> downloadPermissionsImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, FileSharePermissions> getRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, FileSharePermissions>(options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share,
                    OperationContext context) throws Exception {
                return FileRequest.getAcl(share.getTransformedAddress().getUri(this.getCurrentLocation()), options,
                        accessCondition, context);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
            }

            @Override
            public FileSharePermissions preProcessResponse(CloudFileShare share, CloudFileClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                share.updatePropertiesFromResponse(this.getConnection());
                return new FileSharePermissions();
            }

            @Override
            public FileSharePermissions postProcessResponse(HttpURLConnection connection,
                    CloudFileShare share, CloudFileClient client, OperationContext context,
                    FileSharePermissions shareAcl) throws Exception {
                HashMap<String, SharedAccessFilePolicy> accessIds = SharedAccessPolicyHandler.getAccessIdentifiers(
                        this.getConnection().getInputStream(), SharedAccessFilePolicy.class);

                for (final String key : accessIds.keySet()) {
                    shareAcl.getSharedAccessPolicies().put(key, accessIds.get(key));
                }

                return shareAcl;
            }
        };

        return getRequest;
    }

    /**
     * Creates a snapshot of the share.
     *
     * @return A CloudFileShare object that represents the snapshot of the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudFileShare createSnapshot() throws StorageException {
        return this
                .createSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Creates a snapshot of the file share using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return A CloudFileShare object that represents the snapshot of the file share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudFileShare createSnapshot(final AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        return this.createSnapshot(null /* metadata */, accessCondition, options, opContext);
    }

    /**
     * Creates a snapshot of the file share using the specified request options and operation context.
     *
     * @param metadata
     *            A collection of name-value pairs defining the metadata of the snapshot, or null.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return A CloudFileShare object that represents the snapshot of the file share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudFileShare createSnapshot(final HashMap<String, String> metadata,
            final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        assertNoSnapshot();

        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        return ExecutionEngine
                .executeWithRetry(this.fileServiceClient, this,
                        this.createSnapshotImpl(metadata, accessCondition, options), options.getRetryPolicyFactory(),
                        opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, CloudFileShare> createSnapshotImpl(
            final HashMap<String, String> metadata, final AccessCondition accessCondition,
            final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, CloudFileShare> putRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, CloudFileShare>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.snapshotShare(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition);
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudFileShare share, OperationContext context) {
                if (metadata != null) {
                    FileRequest.addMetadata(connection, metadata, context);
                }
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
            }

            @Override
            public CloudFileShare preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                final String snapshotTime = FileResponse.getSnapshotTime(this.getConnection());
                CloudFileShare snapshot = new CloudFileShare(share.getName(), snapshotTime, client);
                snapshot.setProperties(new FileShareProperties(share.properties));

                // use the specified metadata if not null : otherwise share's metadata
                snapshot.setMetadata(metadata != null ? metadata : share.metadata);

                snapshot.updatePropertiesFromResponse(this.getConnection());

                return snapshot;
            }
        };

        return putRequest;
    }

    /**
     * Queries the service for this share's {@link ShareStats}.
     *
     * @return A {@link ShareStats} object for the given storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ShareStats getStats() throws StorageException {
        return getStats(null /* options */, null /* context */);
    }

    /**
     * Queries the service for this share's {@link ShareStats}.
     *
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client
     *            ({@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return A {@link ShareStats} object for the given storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ShareStats getStats(FileRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoSnapshot();

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        return ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                getStatsImpl(options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, ShareStats> getStatsImpl(final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, ShareStats> getRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, ShareStats>(options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.getShareStats(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
            }

            @Override
            public ShareStats preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                share.updatePropertiesFromResponse(this.getConnection());
                return null;
            }

            @Override
            public ShareStats postProcessResponse(HttpURLConnection connection, CloudFileShare share,
                    CloudFileClient client, OperationContext context, ShareStats shareStats) throws Exception {
                return ShareStatsHandler.readShareStatsFromStream(connection.getInputStream());
            }
        };

        return getRequest;
    }

    /**
     * Returns a value that indicates whether the share exists.
     *
     * @return true if the share exists, otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean exists() throws StorageException {
        return this.exists(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Returns a value that indicates whether the share exists, using the specified request options and operation
     * context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return true if the share exists, otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean exists(final AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        return this.exists(false, accessCondition, options, opContext);
    }

    @DoesServiceRequest
    private boolean exists(final boolean primaryOnly, final AccessCondition accessCondition,
            FileRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        return ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Boolean> existsImpl(final boolean primaryOnly,
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, Boolean> getRequest = new StorageRequest<CloudFileClient, CloudFileShare, Boolean>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(
                        primaryOnly ? RequestLocationMode.PRIMARY_ONLY : RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.getShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, share.snapshotID);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
            }

            @Override
            public Boolean preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
                    share.updatePropertiesFromResponse(this.getConnection());
                    final FileShareAttributes attributes = FileResponse.getFileShareAttributes(this.getConnection(),
                            client.isUsePathStyleUris());
                    share.metadata = attributes.getMetadata();
                    share.properties = attributes.getProperties();

                    return Boolean.valueOf(true);
                }
                else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
                    return Boolean.valueOf(false);
                }
                else {
                    this.setNonExceptionedRetryableFailure(true);
                    // return false instead of null to avoid SCA issues
                    return false;
                }
            }
        };

        return getRequest;
    }

    private void updatePropertiesFromResponse(HttpURLConnection request) {
        // ETag
        this.getProperties().setEtag(request.getHeaderField(Constants.HeaderConstants.ETAG));

        // Last Modified
        if (0 != request.getLastModified()) {
            final Calendar lastModifiedCalendar = Calendar.getInstance(Utility.LOCALE_US);
            lastModifiedCalendar.setTimeZone(Utility.UTC_ZONE);
            lastModifiedCalendar.setTime(new Date(request.getLastModified()));
            this.getProperties().setLastModified(lastModifiedCalendar.getTime());
        }
    }

    /**
     * Asserts that the share is not a snapshot.
     */
    protected void assertNoSnapshot() {
        if (isSnapshot()) {
            throw new IllegalArgumentException(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT);
        }
    }

    /**
     * Returns a shared access signature for the share. Note this does not contain the leading "?".
     *
     * @param policy
     *            An {@link SharedAccessFilePolicy} object that represents the access policy for the
     *            shared access signature.
     * @param groupPolicyIdentifier
     *            A String which represents the share-level access policy.
     *
     * @return A String which represents a shared access signature for the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws InvalidKeyException
     *             If the key is invalid.
     */
    public String generateSharedAccessSignature(final SharedAccessFilePolicy policy, final String groupPolicyIdentifier)
            throws InvalidKeyException, StorageException {

        return this.generateSharedAccessSignature(policy, groupPolicyIdentifier, null /* IP range */, null /* protocols */);
    }

    /**
     * Returns a shared access signature for the share. Note this does not contain the leading "?".
     *
     * @param policy
     *            An {@link SharedAccessFilePolicy} object that represents the access policy for the
     *            shared access signature.
     * @param groupPolicyIdentifier
     *            A String which represents the share-level access policy.
     * @param ipRange
     *            A {@link IPRange} object containing the range of allowed IP addresses.
     * @param protocols
     *            A {@link SharedAccessProtocols} representing the allowed Internet protocols.
     *
     * @return A String which represents a shared access signature for the share.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws InvalidKeyException
     *             If the key is invalid.
     */
    public String generateSharedAccessSignature(
            final SharedAccessFilePolicy policy, final String groupPolicyIdentifier, final IPRange ipRange,
            final SharedAccessProtocols protocols)
            throws InvalidKeyException, StorageException {

        if (!StorageCredentialsHelper.canCredentialsSignRequest(this.fileServiceClient.getCredentials())) {
            final String errorMessage = SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY;
            throw new IllegalArgumentException(errorMessage);
        }

        final String resourceName = this.getSharedAccessCanonicalName();

        final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(
                policy, null /* SharedAccessHeaders */, groupPolicyIdentifier, resourceName,
                ipRange, protocols, this.fileServiceClient);

        final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(
                policy, null /* SharedAccessHeaders */, groupPolicyIdentifier, "s", ipRange, protocols, signature);

        return builder.toString();
    }

    /**
     * Returns the canonical name for shared access.
     *
     * @return the canonical name for shared access.
     */
    private String getSharedAccessCanonicalName() {
        String accountName = this.getServiceClient().getCredentials().getAccountName();
        String shareName = this.getName();

        return String.format("/%s/%s/%s", SR.FILE, accountName, shareName);
    }

    /**
     * Uploads the share's metadata.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadMetadata() throws StorageException {
        this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the share's metadata using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        assertNoSnapshot();

        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> uploadMetadataImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, Void> putRequest = new StorageRequest<CloudFileClient, CloudFileShare, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.setShareMetadata(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition);
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudFileShare share, OperationContext context) {
                FileRequest.addMetadata(connection, share.metadata, context);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
            }

            @Override
            public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                share.updatePropertiesFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Updates the share's properties on the storage service.
     *

     * Use {@link CloudFileShare#downloadAttributes} to retrieve the latest values for
     * the share's properties and metadata from the Microsoft Azure storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void uploadProperties() throws StorageException {
        this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */);
    }

    /**
     * Updates the share's properties using the request options, and operation context.
     *

     * Use {@link CloudFileShare#downloadAttributes} to retrieve the latest values for
     * the share's properties and metadata from the Microsoft Azure storage service.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void uploadProperties(
            AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        assertNoSnapshot();

        if (this.properties != null && this.properties.getShareQuota() != null) {
            Utility.assertInBounds("Share Quota", this.properties.getShareQuota(), 1, FileConstants.MAX_SHARE_QUOTA);
        }

        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                this.uploadPropertiesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> uploadPropertiesImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileShare, Void> putRequest =
                new StorageRequest<CloudFileClient, CloudFileShare, Void>(options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share, OperationContext context)
                    throws Exception {
                return FileRequest.setShareProperties(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, share.properties);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
            }

            @Override
            public Void preProcessResponse(CloudFileShare share, CloudFileClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                share.updatePropertiesFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Uploads the share's permissions.
     *
     * @param permissions
     *            A {@link FileSharePermissions} object that represents the permissions to upload.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPermissions(final FileSharePermissions permissions) throws StorageException {
        this.uploadPermissions(permissions, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the share's permissions using the specified request options and operation context.
     *
     * @param permissions
     *            A {@link FileSharePermissions} object that represents the permissions to upload.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPermissions(final FileSharePermissions permissions, final AccessCondition accessCondition,
            FileRequestOptions options, OperationContext opContext) throws StorageException {
        assertNoSnapshot();

        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                uploadPermissionsImpl(permissions, accessCondition, options), options.getRetryPolicyFactory(),
                opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileShare, Void> uploadPermissionsImpl(
            final FileSharePermissions permissions, final AccessCondition accessCondition,
            final FileRequestOptions options) throws StorageException {
        try {
            final StringWriter outBuffer = new StringWriter();
            SharedAccessPolicySerializer.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(),
                    outBuffer);
            final byte[] aclBytes = outBuffer.toString().getBytes(Constants.UTF8_CHARSET);
            final StorageRequest<CloudFileClient, CloudFileShare, Void> putRequest =
                    new StorageRequest<CloudFileClient, CloudFileShare, Void>(options, this.getStorageUri()) {
                @Override
                public HttpURLConnection buildRequest(CloudFileClient client, CloudFileShare share,
                        OperationContext context) throws Exception {
                    this.setSendStream(new ByteArrayInputStream(aclBytes));
                    this.setLength((long) aclBytes.length);
                    return FileRequest.setAcl(share.getTransformedAddress().getUri(this.getCurrentLocation()),
                            options, context, accessCondition);
                }

                @Override
                public void signRequest(HttpURLConnection connection, CloudFileClient client, OperationContext context)
                        throws Exception {
                    StorageRequest.signBlobQueueAndFileRequest(connection, client, aclBytes.length, context);
                }

                @Override
                public Void preProcessResponse(CloudFileShare share, CloudFileClient client,
                        OperationContext context) throws Exception {
                    if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                        this.setNonExceptionedRetryableFailure(true);
                        return null;
                    }

                    share.updatePropertiesFromResponse(this.getConnection());
                    return null;
                }
            };

            return putRequest;
        }
        catch (IllegalArgumentException e) {
            // The request was not even made. There was an error while trying to write the permissions. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
        catch (IllegalStateException e) {
            // The request was not even made. There was an error while trying to write the permissions. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
        catch (IOException e) {
            // The request was not even made. There was an error while trying to write the permissions. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
    }

    /**
     * Returns a reference to a {@link CloudFileDirectory} object that represents the root file directory within this
     * share.
     *
     * @return A {@link CloudFileDirectory} reference to the root directory for this share.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public CloudFileDirectory getRootDirectoryReference() throws StorageException, URISyntaxException {
        return new CloudFileDirectory(this.storageUri, "", this);
    }

    /**
     * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties.
     *
     * @param completeUri
     *            A {@link StorageUri} object which represents the complete URI.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials)
            throws StorageException {
       Utility.assertNotNull("completeUri", completeUri);

        if (!completeUri.isAbsolute()) {
            throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()));
        }

        this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri);

        final HashMap<String, String[]> queryParameters = PathUtility.parseQueryString(completeUri.getQuery());

        final String[] snapshotIDs = queryParameters.get(Constants.QueryConstants.SHARE_SNAPSHOT);
        if (snapshotIDs != null && snapshotIDs.length > 0) {
            this.snapshotID = snapshotIDs[0];
        }

        final StorageCredentialsSharedAccessSignature parsedCredentials =
                SharedAccessSignatureHelper.parseQuery(completeUri);

        if (credentials != null && parsedCredentials != null) {
            throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED);
        }

        try {
            final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri());
            this.fileServiceClient = new CloudFileClient(PathUtility.getServiceClientBaseAddress(
                    this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials);
            this.name = PathUtility.getShareNameFromUri(this.storageUri.getPrimaryUri(), usePathStyleUris);
        }
        catch (final URISyntaxException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
    }

    /**
     * Returns the File service client associated with this share.
     *
     * @return A {@link CloudFileClient} object that represents the service client associated with this share.
     */
    public CloudFileClient getServiceClient() {
        return this.fileServiceClient;
    }

    /**
     * Returns the URI for this share.
     *
     * @return The absolute URI to the share.
     */
    public URI getUri() {
        return this.storageUri.getPrimaryUri();
    }

    /**
     * Returns the snapshotID for this share.
     *
     * @return The snapshotID as a string for this share.
     */
    public final String getSnapshot() {
        return this.snapshotID;
    }

    /**
     * Indicates whether this share is a snapshot.
     *
     * @return true if the share is a snapshot, otherwise false.
     *
     * @see DeleteSnapshotsOption
     */
    public final boolean isSnapshot() {
        return this.snapshotID != null;
    }

    /**
     * Returns the list of URIs for all locations.
     *
     * @return A {@link StorageUri} object which represents the list of URIs for all locations.
     */
    public StorageUri getStorageUri() {
        return this.storageUri;
    }

    /**
     * Returns the snapshot or shared access signature qualified URI for this share.
     *
     * @return A java.net.URI object that represents the snapshot or shared access signature.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public final URI getQualifiedUri() throws URISyntaxException, StorageException {
        if (this.isSnapshot()) {
            return PathUtility.addToQuery(this.getUri(), String.format("sharesnapshot=%s", this.snapshotID));
        }

        return this.fileServiceClient.getCredentials().transformUri(this.getUri());
    }

    /**
     * Returns the name of the share.
     *
     * @return A String that represents the name of the share.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Returns the metadata for the share. This value is initialized with the metadata from the share by a call to
     * {@link #downloadAttributes}, and is set on the share with a call to {@link #uploadMetadata}.
     *
     * @return A java.util.HashMap object that represents the metadata for the share.
     */
    public HashMap<String, String> getMetadata() {
        return this.metadata;
    }

    /**
     * Returns the properties for the share.
     *
     * @return A {@link FileShareProperties} object that represents the properties for the share.
     */
    public FileShareProperties getProperties() {
        return this.properties;
    }

    /**
     * Sets the metadata collection of name-value pairs to be set on the share with an {@link #uploadMetadata} call.
     * This collection will overwrite any existing share metadata. If this is set to an empty collection, the
     * share metadata will be cleared on an {@link #uploadMetadata} call.
     *
     * @param metadata
     *            A java.util.HashMap object that represents the metadata being assigned to the share.
     */
    public void setMetadata(final HashMap<String, String> metadata) {
        this.metadata = metadata;
    }

    /**
     * Sets the properties for the share.
     *
     * @param properties
     *            A {@link FileShareProperties} object that represents the properties being assigned to the
     *            share.
     */
    public void setProperties(final FileShareProperties properties) {
        this.properties = properties;
    }

    /**
     * Returns the URI after applying the authentication transformation.
     *
     * @return A java.net.URI object that represents the URI after applying the authentication
     *         transformation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    private StorageUri getTransformedAddress() throws URISyntaxException, StorageException {
        return this.fileServiceClient.getCredentials().transformUri(this.storageUri);
    }
}


/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.microsoft.azure.storage.samples.table;

import java.util.UUID;

import android.os.AsyncTask;
import android.widget.TextView;

import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.samples.MainActivity;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.table.*;
import com.microsoft.azure.storage.table.TableQuery.QueryComparisons;

/**
 * This sample illustrates basic usage of the various Table Primitives provided
 * in the Storage Client Library including TableOperation, TableBatchOperation,
 * and TableQuery.
 */
public class TableGettingStartedTask extends AsyncTask<String, Void, Void> {

    protected static CloudTableClient tableClient;
    protected static CloudTable table;
    protected final static String tableName = "tablebasics";

    private TextView view;
    private MainActivity act;

    public TableGettingStartedTask(MainActivity act, TextView view) {
        this.view = view;
        this.act = act;
    }

    @Override
    protected Void doInBackground(String... arg0) {

        act.printSampleStartInfo("TableBasics");

        try {
            // Setup the cloud storage account.
            CloudStorageAccount account = CloudStorageAccount
                    .parse(MainActivity.storageConnectionString);

            // Create a table service client.
            tableClient = account.createCloudTableClient();

            // Retrieve a reference to a table.
            // Append a random UUID to the end of the table name so that this
            // sample can be run more than once in quick succession.
            table = tableClient.getTableReference(tableName
                    + UUID.randomUUID().toString().replace("-", ""));

            // Create the table if it doesn't already exist.
            table.createIfNotExists();

            // Illustrates how to list the tables.
            this.BasicListing();

            // Illustrates how to form and execute a single insert operation.
            this.BasicInsertEntity();

            // Illustrates how to form and execute a batch operation.
            this.BasicBatch();

            // Illustrates how to form and execute a query operation.
            this.BasicQuery();

            // Illustrates how to form and execute an upsert operation.
            this.BasicUpsert();

            // Illustrates how to form and execute an entity delete operation.
            this.BasicDeleteEntity();

            // Delete the table.
            table.deleteIfExists();

        } catch (Throwable t) {
            act.printException(t);
        }

        act.printSampleCompleteInfo("TableBasics");

        return null;
    }

    /**
     * Illustrates how to form and execute a single insert operation.
     *
     * @throws StorageException
     */
    public void BasicInsertEntity() throws StorageException {
        // Note: the limitations on an insert operation are
        // - the serialized payload must be 1 MB or less
        // - up to 252 properties in addition to the partition key, row key and
        // timestamp. 255 properties in total
        // - the serialized payload of each property must be 64 KB or less

        // Create a new customer entity.
        CustomerEntity customer1 = new CustomerEntity("Harp", "Walter");
        customer1.setEmail("Walter@contoso.com");
        customer1.setPhoneNumber("425-555-0101");

        // Create an operation to add the new customer to the tablebasics table.
        TableOperation insertCustomer1 = TableOperation.insert(customer1);

        // Submit the operation to the table service.
        table.execute(insertCustomer1);
    }

    /**
     * Illustrates how to form and execute a batch operation.
     *
     * @throws StorageException
     */
    public void BasicBatch() throws StorageException {
        // Note: the limitations on a batch operation are
        // - up to 100 operations
        // - all operations must share the same PartitionKey
        // - if a retrieve is used it can be the only operation in the batch
        // - the serialized batch payload must be 4 MB or less

        // Define a batch operation.
        TableBatchOperation batchOperation = new TableBatchOperation();

        // Create a customer entity to add to the table.
        CustomerEntity customer = new CustomerEntity("Smith", "Jeff");
        customer.setEmail("Jeff@contoso.com");
        customer.setPhoneNumber("425-555-0104");
        batchOperation.insert(customer);

        // Create another customer entity to add to the table.
        CustomerEntity customer2 = new CustomerEntity("Smith", "Ben");
        customer2.setEmail("Ben@contoso.com");
        customer2.setPhoneNumber("425-555-0102");
        batchOperation.insert(customer2);

        // Create a third customer entity to add to the table.
        CustomerEntity customer3 = new CustomerEntity("Smith", "Denise");
        customer3.setEmail("Denise@contoso.com");
        customer3.setPhoneNumber("425-555-0103");
        batchOperation.insert(customer3);

        // Execute the batch of operations on the "tablebasics" table.
        table.execute(batchOperation);
    }

    /**
     * Illustrates how to form and execute a query operation.
     *
     * @throws StorageException
     */
    public void BasicQuery() throws StorageException {
        // Retrieve a single entity.
        // Retrieve the entity with partition key of "Smith" and row key of
        // "Jeff".
        TableOperation retrieveSmithJeff = TableOperation.retrieve("Smith",
                "Jeff", CustomerEntity.class);

        // Submit the operation to the table service and get the specific
        // entity.
        @SuppressWarnings("unused")
        CustomerEntity specificEntity = table.execute(retrieveSmithJeff)
                .getResultAsType();

        // Retrieve all entities in a partition.
        // Create a filter condition where the partition key is "Smith".
        String partitionFilter = TableQuery.generateFilterCondition(
                "PartitionKey", QueryComparisons.EQUAL, "Smith");

        // Specify a partition query, using "Smith" as the partition key filter.
        TableQuery<CustomerEntity> partitionQuery = TableQuery.from(
                CustomerEntity.class).where(partitionFilter);

        // Loop through the results, displaying information about the entity.
        for (CustomerEntity entity : table.execute(partitionQuery)) {
            act.outputText(
                    view,
                    entity.getPartitionKey() + " " + entity.getRowKey() + "\t"
                            + entity.getEmail() + "\t"
                            + entity.getPhoneNumber());
        }
    }

    /**
     * Illustrates how to form and execute an upsert operation.
     *
     * @throws StorageException
     */
    public void BasicUpsert() throws StorageException {
        // Retrieve the entity with partition key of "Smith" and row key of
        // "Jeff".
        TableOperation retrieveSmithJeff = TableOperation.retrieve("Smith",
                "Jeff", CustomerEntity.class);

        // Submit the operation to the table service and get the specific
        // entity.
        CustomerEntity specificEntity = table.execute(retrieveSmithJeff)
                .getResultAsType();

        // Specify a new phone number.
        specificEntity.setPhoneNumber("425-555-0105");

        // Create an operation to replace the entity.
        TableOperation replaceEntity = TableOperation.merge(specificEntity);

        // Submit the operation to the table service.
        table.execute(replaceEntity);
    }

    /**
     * Illustrates how to form and execute an entity delete operation.
     *
     * @throws StorageException
     */
    public void BasicDeleteEntity() throws StorageException {
        // Create an operation to retrieve the entity with partition key of
        // "Smith" and row key of "Jeff".
        TableOperation retrieveSmithJeff = TableOperation.retrieve("Smith",
                "Jeff", CustomerEntity.class);

        // Retrieve the entity with partition key of "Smith" and row key of
        // "Jeff".
        CustomerEntity entitySmithJeff = table.execute(retrieveSmithJeff)
                .getResultAsType();

        // Create an operation to delete the entity.
        TableOperation deleteSmithJeff = TableOperation.delete(entitySmithJeff);

        // Submit the delete operation to the table service.
        table.execute(deleteSmithJeff);
    }

    /**
     * Illustrates how to list the tables.
     */
    public void BasicListing() {
        // List the tables with a given prefix.
        Iterable<String> listTables = tableClient.listTables(tableName, null,
                null);
        for (String s : listTables) {
            act.outputText(view, String.format("List of tables: %s", s));
        }
    }
}