/**
 * 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.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
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.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultContinuationType;
import com.microsoft.azure.storage.ResultSegment;
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.BaseResponse;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.LazySegmentedIterable;
import com.microsoft.azure.storage.core.ListResponse;
import com.microsoft.azure.storage.core.ListingContext;
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.SegmentedStorageRequest;
import com.microsoft.azure.storage.core.SharedAccessSignatureHelper;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.Utility;

/**
 * Represents a virtual directory of files.
 *

 * Directories, which are encapsulated as {@link CloudFileDirectory} objects, hold files and can also contain
 * sub-directories.
 */
public final class CloudFileDirectory implements ListFileItem {
    /**
     * Holds the share reference.
     */
    private CloudFileShare share;

    /**
     * Represents the parent directory.
     */
    private CloudFileDirectory parent;

    /**
     * Represents the File client.
     */
    private CloudFileClient fileServiceClient;

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

    /**
     * Holds the name of the directory.
     */
    private String name;

    /**
     * Represents the directory metadata.
     */
    private HashMap<String, String> metadata = new HashMap<String, String>();

    /**
     * Holds a FileDirectoryProperties object that holds the directory's system properties.
     */
    private FileDirectoryProperties properties = new FileDirectoryProperties();

    /**
     * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory.
     *
     * @param directoryAbsoluteUri
     *            A {@link URI} that represents the file directory's address.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public CloudFileDirectory(final URI directoryAbsoluteUri) throws StorageException, URISyntaxException {
        this(new StorageUri(directoryAbsoluteUri));
    }

    /**
     * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory.
     *
     * @param directoryAbsoluteUri
     *            A {@link StorageUri} that represents the file directory's address.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public CloudFileDirectory(final StorageUri directoryAbsoluteUri) throws StorageException, URISyntaxException {
        this(directoryAbsoluteUri, (StorageCredentials) null);
    }

    /**
     * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory
     * and credentials.
     *
     * @param directoryAbsoluteUri
     *            A {@link URI} that represents the file directory's address.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public CloudFileDirectory(final URI directoryAbsoluteUri, final StorageCredentials credentials)
            throws StorageException, URISyntaxException {
        this(new StorageUri(directoryAbsoluteUri), credentials);
    }

    /**
     * Creates an instance of the CloudFileDirectory class using an absolute URI to the directory
     * and credentials.
     *
     * @param directoryAbsoluteUri
     *            A {@link StorageUri} that represents the file directory's address.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public CloudFileDirectory(final StorageUri directoryAbsoluteUri, final StorageCredentials credentials)
            throws StorageException, URISyntaxException {
        this.parseQueryAndVerify(directoryAbsoluteUri, credentials);
    }

    /**
     * Creates an instance of the CloudFileDirectory class using the specified address, share,
     * and client.
     *
     * @param uri
     *            A {@link StorageUri} that represents the file directory's address.
     * @param directoryName
     *            A String that represents the name of the directory.
     * @param share
     *            A {@link CloudFileShare} object that represents the associated file share.
     */
    protected CloudFileDirectory(final StorageUri uri, final String directoryName, final CloudFileShare share) {
        Utility.assertNotNull("uri", uri);
        Utility.assertNotNull("directoryName", directoryName);
        Utility.assertNotNull("share", share);

        this.name = directoryName;
        this.fileServiceClient = share.getServiceClient();
        this.share = share;
        this.storageUri = uri;
    }

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

    /**
     * Creates the directory 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.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public void create(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        this.getShare().assertNoSnapshot();

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

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

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

            @Override
            public void setHeaders(HttpURLConnection connection, CloudFileDirectory directory, OperationContext context) {
                FileRequest.addMetadata(connection, directory.getMetadata(), context);
            }

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

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

                // Set attributes
                final FileDirectoryAttributes attributes = FileResponse
                        .getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris());
                directory.setProperties(attributes.getProperties());
                this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                return null;
            }
        };

        return putRequest;
    }

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

    /**
     * Creates the directory 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 directory did not already exist and was created; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public boolean createIfNotExists(FileRequestOptions options, OperationContext opContext) throws StorageException, URISyntaxException {
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

        this.getShare().assertNoSnapshot();

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

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

    /**
     * Deletes the directory using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @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.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public void delete(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        this.getShare().assertNoSnapshot();

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

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

    private StorageRequest<CloudFileClient, CloudFileDirectory, Void> deleteDirectoryImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileDirectory, Void> putRequest =
                new StorageRequest<CloudFileClient, CloudFileDirectory, Void>(options, this.getStorageUri()) {
            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory,
                    OperationContext context) throws Exception {
                return FileRequest.deleteDirectory(directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context,
                        accessCondition);
            }

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

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

                return null;
            }
        };

        return putRequest;
    }

    /**
     * Deletes the directory if it exists.
     *
     * @return true if the directory did not already exist and was created; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public boolean deleteIfExists() throws StorageException, URISyntaxException {
        return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Deletes the directory if it exists using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @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 directory existed and was deleted; otherwise, false.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public boolean deleteIfExists(AccessCondition accessCondition, FileRequestOptions options,
            OperationContext opContext) throws StorageException, URISyntaxException {
        options = FileRequestOptions.populateAndApplyDefaults(options, this.fileServiceClient);

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

    /**
     * Returns a value that indicates whether the directory exists.
     *
     * @return true if the directory 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 directory 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 directory 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, CloudFileDirectory, Boolean> existsImpl(final boolean primaryOnly,
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileDirectory, Boolean> getRequest =
                new StorageRequest<CloudFileClient, CloudFileDirectory, Boolean>(options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory,
                    OperationContext context) throws Exception {
                return FileRequest.getDirectoryProperties(
                        directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context,
                        accessCondition, directory.getShare().snapshotID);
            }

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

            @Override
            public Boolean preProcessResponse(CloudFileDirectory directory, CloudFileClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
                    directory.updatePropertiesFromResponse(this.getConnection());

                    // Set properties
                    final FileDirectoryAttributes attributes =
                            FileResponse.getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris());
                    directory.setMetadata(attributes.getMetadata());
                    directory.setProperties(attributes.getProperties());

                    return Boolean.valueOf(true);
                }
                else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
                    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());
        }
    }

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

    /**
     * Uploads the directory's metadata using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @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.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public void uploadMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        this.getShare().assertNoSnapshot();

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

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

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

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

            @Override
            public void setHeaders(HttpURLConnection connection, CloudFileDirectory directory, OperationContext context) {
                FileRequest.addMetadata(connection, directory.getMetadata(), context);
            }

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

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

                directory.updatePropertiesFromResponse(this.getConnection());
                this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Downloads the directory's 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 directory's properties using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @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, CloudFileDirectory, Void> downloadAttributesImpl(
            final AccessCondition accessCondition, final FileRequestOptions options) {
        final StorageRequest<CloudFileClient, CloudFileDirectory, Void> getRequest =
                new StorageRequest<CloudFileClient, CloudFileDirectory, Void>(options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory,
                    OperationContext context) throws Exception {
                return FileRequest.getDirectoryProperties(
                        directory.getTransformedAddress().getUri(this.getCurrentLocation()), options, context,
                        accessCondition, directory.getShare().snapshotID);
            }

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

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

                // Set properties
                final FileDirectoryAttributes attributes =
                        FileResponse.getFileDirectoryAttributes(this.getConnection(), client.isUsePathStyleUris());
                directory.setMetadata(attributes.getMetadata());
                directory.setProperties(attributes.getProperties());
                return null;
            }
        };

        return getRequest;
    }

    /**
     * Returns an enumerable collection of file and directory items for the directory.
     *
     * @return An enumerable collection of {@link ListFileItem} objects that represent the file and directory items in
     *         this directory.
     */
    @DoesServiceRequest
    public Iterable<ListFileItem> listFilesAndDirectories() {
        return this.listFilesAndDirectories(null /* prefix */, null /* options */, null /* opContext */);
    }

    /**
     * Returns an enumerable collection of file and directory items for the directory.
     *
     * @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 An enumerable collection of {@link ListFileItem} objects that represent the file and directory items in
     *         this directory.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @DoesServiceRequest
    public Iterable<ListFileItem> listFilesAndDirectories(FileRequestOptions options, OperationContext opContext) {
        return listFilesAndDirectories(null /* prefix */, options, opContext);
    }

    /**
     * Returns an enumerable collection of file and directory items for the directory.
     *
     * @param prefix
     *            A string containing the file or directory name prefix.
     * @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 An enumerable collection of {@link ListFileItem} objects that represent the file and directory items in
     *         this directory.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @DoesServiceRequest
    public Iterable<ListFileItem> listFilesAndDirectories(
            String prefix, FileRequestOptions options, OperationContext opContext) {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();

        return new LazySegmentedIterable<CloudFileClient, CloudFileDirectory, ListFileItem>(
                this.listFilesAndDirectoriesSegmentedImpl(prefix, null /* maxResults */, options, segmentedRequest),
                this.fileServiceClient, this,
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of files and directories for this File service client.
     *
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link ListFileItem} objects that represent the files and directories.
     * @throws StorageException
     */
    @DoesServiceRequest
    public ResultSegment<ListFileItem> listFilesAndDirectoriesSegmented() throws StorageException {
        return this.listFilesAndDirectoriesSegmented(null /* prefix */, null /* maxResults */,
                null /* continuationToken */, null /* options */, null /* opContext */);
    }

    /**
     * Returns a result segment of an enumerable collection of files and directories for this directory, using the
     * specified listing details options, request options, and operation context.
     *
     * @param maxResults
     *            The maximum number of results to retrieve.  If null or greater
     *            than 5000, the server will return up to 5,000 items.  Must be at least 1.
     * @param continuationToken
     *            A {@link ResultContinuation} object that represents a continuation token
     *            returned by a previous listing operation.
     * @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 ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link ListFileItem} objects that represent the files and directories in this directory.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<ListFileItem> listFilesAndDirectoriesSegmented(final Integer maxResults,
            final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        return listFilesAndDirectoriesSegmented(null, maxResults, continuationToken, options, opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of files and directories for this directory, using the
     * specified listing details options, request options, and operation context.
     *
     * @param prefix
     *            A string containing the file or directory name prefix.
     * @param maxResults
     *            The maximum number of results to retrieve.  If null or greater
     *            than 5000, the server will return up to 5,000 items.  Must be at least 1.
     * @param continuationToken
     *            A {@link ResultContinuation} object that represents a continuation token
     *            returned by a previous listing operation.
     * @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 ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link ListFileItem} objects that represent the files and directories in this directory.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<ListFileItem> listFilesAndDirectoriesSegmented(final String prefix, final Integer maxResults,
            final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        Utility.assertContinuationType(continuationToken, ResultContinuationType.FILE);

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();
        segmentedRequest.setToken(continuationToken);

        return ExecutionEngine.executeWithRetry(this.fileServiceClient, this,
                this.listFilesAndDirectoriesSegmentedImpl(prefix, maxResults, options, segmentedRequest),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, CloudFileDirectory, ResultSegment<ListFileItem>> listFilesAndDirectoriesSegmentedImpl(
            final String prefix, final Integer maxResults, final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) {

        Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.FILE);

        final ListingContext listingContext = new ListingContext(prefix, maxResults);

        final StorageRequest<CloudFileClient, CloudFileDirectory, ResultSegment<ListFileItem>> getRequest =
                new StorageRequest<CloudFileClient, CloudFileDirectory, ResultSegment<ListFileItem>>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(Utility.getListingLocationMode(segmentedRequest.getToken()));
            }

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, CloudFileDirectory directory,
                    OperationContext context) throws Exception {
                listingContext.setMarker(segmentedRequest.getToken() != null ? segmentedRequest.getToken()
                        .getNextMarker() : null);
                return FileRequest.listFilesAndDirectories(
                        directory.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, listingContext, directory.getShare().snapshotID);
            }

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

            @Override
            public ResultSegment<ListFileItem> preProcessResponse(CloudFileDirectory directory, CloudFileClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }

            @Override
            public ResultSegment<ListFileItem> postProcessResponse(HttpURLConnection connection,
                    CloudFileDirectory directory, CloudFileClient client, OperationContext context,
                    ResultSegment<ListFileItem> storageObject) throws Exception {
                final ListResponse<ListFileItem> response = FileListHandler.getFileAndDirectoryList(this
                        .getConnection().getInputStream(), directory);
                ResultContinuation newToken = null;

                if (response.getNextMarker() != null) {
                    newToken = new ResultContinuation();
                    newToken.setNextMarker(response.getNextMarker());
                    newToken.setContinuationType(ResultContinuationType.FILE);
                    newToken.setTargetLocation(this.getResult().getTargetLocation());
                }

                final ResultSegment<ListFileItem> resSegment = new ResultSegment<ListFileItem>(response.getResults(),
                        response.getMaxResults(), newToken);

                // Important for listFilesAndDirectories because this is required by the lazy iterator between executions.
                segmentedRequest.setToken(resSegment.getContinuationToken());
                return resSegment;
            }
        };

        return getRequest;
    }

    /**
     * Returns a reference to a {@link CloudFile} object that represents a file in this directory.
     *
     * @param fileName
     *            A String that represents the name of the file.
     *
     * @return A {@link CloudFile} object that represents a reference to the specified file.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudFile getFileReference(final String fileName) throws URISyntaxException, StorageException {
        Utility.assertNotNullOrEmpty("fileName", fileName);

        StorageUri subdirectoryUri = PathUtility.appendPathToUri(this.storageUri, fileName);
        return new CloudFile(subdirectoryUri, fileName, this.getShare());
    }

    /**
     * Returns a reference to a {@link CloudFileDirectory} object that represents a directory in this directory.
     *
     * @param itemName
     *            A String that represents the name of the directory.
     *
     * @return A {@link CloudFileDirectory} object that represents a reference to the specified directory.
     *
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     * @throws StorageException
     */
    public CloudFileDirectory getDirectoryReference(final String itemName) throws URISyntaxException,
            StorageException {
        Utility.assertNotNullOrEmpty("itemName", itemName);

        StorageUri subdirectoryUri = PathUtility.appendPathToUri(this.storageUri, itemName);
        return new CloudFileDirectory(subdirectoryUri, itemName, this.getShare());
    }

    /**
     * Returns the URI for this directory.
     *
     * @return A java.net.URI object that represents the URI for this directory.
     */
    @Override
    public URI getUri() {
        return this.storageUri.getPrimaryUri();
    }

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

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

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

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

    /**
     * Returns the {@link FileDirectoryProperties} object that holds the directory's system properties.
     *
     * @return A {@link FileDirectoryProperties} object that holds the system properties associated with the
     *         directory.
     */
    public FileDirectoryProperties getProperties() {
        return this.properties;
    }

    /**
     * Returns the {@link CloudFileDirectory} parent directory associated with this directory.
     *
     * @return An {@link CloudFileDirectory} object that represents the parent directory associated with the directory.
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Override
    public CloudFileDirectory getParent() throws URISyntaxException, StorageException {
        if (this.parent == null) {
            final String parentName = CloudFile.getParentNameFromURI(this.getStorageUri(), this.getShare());

            if (parentName != null) {
                StorageUri parentURI = PathUtility.appendPathToUri(this.getShare().getStorageUri(), parentName);
                this.parent = new CloudFileDirectory(parentURI, this.getServiceClient().getCredentials());
            }
        }
        return this.parent;
    }

    /**
     * Returns the share for this directory.
     *
     * @return A {@link CloudFileShare} that represents the share for this directory.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @Override
    public CloudFileShare getShare() throws StorageException, URISyntaxException {
        if (this.share == null) {
            this.share = this.fileServiceClient.getShareReference(PathUtility.getShareNameFromUri(this.getUri(),
                    this.fileServiceClient.isUsePathStyleUris()));
        }
        return this.share;
    }

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

    /**
     * Sets the share for the directory.
     *
     * @param share
     *            A {@link CloudFileShare} object that represents the share being assigned to the directory.
     */
    protected final void setShare(final CloudFileShare share) {
        this.share = share;
    }

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

    /**
     * Sets the list of URIs for all locations.
     *
     * @param storageUri
     *            A {@link StorageUri} that represents the list of URIs for all locations.
     */
    protected void setStorageUri(final StorageUri storageUri) {
        this.storageUri = storageUri;
    }

    /**
     * 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.
     * @throws URISyntaxException
     */
    private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials)
            throws StorageException, URISyntaxException {
       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 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.getDirectoryNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris);
        }
        catch (final URISyntaxException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }

        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.getShare().snapshotID = snapshotIDs[0];
        }
    }

    /**
     * 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.queue;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.BaseRequest;
import com.microsoft.azure.storage.core.ListingContext;
import com.microsoft.azure.storage.core.UriQueryBuilder;

/**
 * RESERVED FOR INTERNAL USE. Provides a set of methods for constructing web
 * requests for queue operations.
 */
final class QueueRequest {

    private static final String METADATA = "metadata";

    private static final String POP_RECEIPT = "popreceipt";

    private static final String PEEK_ONLY = "peekonly";

    private static final String NUMBER_OF_MESSAGES = "numofmessages";

    private static final String VISIBILITY_TIMEOUT = "visibilitytimeout";

    private static final String MESSAGE_TTL = "messagettl";

    /**
     * Adds user-defined metadata to the web request as one or more name-value
     * pairs.
     *
     * @param request
     *            The HttpURLConnection web request to add the
     *            metadata to.
     * @param metadata
     *            A HashMap containing the user-defined metadata to
     *            add.
     * @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.
     */
    public static void addMetadata(final HttpURLConnection request, final HashMap<String, String> metadata,
            final OperationContext opContext) {
        BaseRequest.addMetadata(request, metadata, opContext);
    }

    /**
     * Constructs a web request to clear all the messages in the queue. Sign the
     * web request with a length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection clearMessages(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws URISyntaxException, IOException, StorageException {
        return BaseRequest.delete(uri, queueOptions, null, opContext);
    }

    /**
     * Constructs a web request to create a new queue. Sign the web request with
     * a length of 0.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection create(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        return BaseRequest.create(uri, queueOptions, null, opContext);
    }

    /**
     * Constructs a web request to delete the queue. Sign the web request with a
     * length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection delete(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        return BaseRequest.delete(uri, queueOptions, null, opContext);
    }

    /**
     * Constructs a web request to delete a message from the queue. Sign the web
     * request with a length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param popReceipt
     *            A String that contains the pop receipt value
     *            returned from an earlier call to {@link CloudQueueMessage#getPopReceipt} for the message to
     *            delete.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws IOException
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection deleteMessage(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final String popReceipt) throws URISyntaxException, IOException,
            StorageException {

        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(POP_RECEIPT, popReceipt);

        final HttpURLConnection request = BaseRequest.delete(uri, queueOptions, builder, opContext);

        return request;
    }

    /**
     * Constructs a web request to download user-defined metadata and the
     * approximate message count for the queue. Sign the web request with a
     * length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection downloadAttributes(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        UriQueryBuilder builder = new UriQueryBuilder();

        builder.add(Constants.QueryConstants.COMPONENT, METADATA);
        final HttpURLConnection retConnection = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        retConnection.setRequestMethod(Constants.HTTP_HEAD);

        return retConnection;
    }

    /**
     * Constructs a web request to return a listing of all queues in this
     * storage account. Sign the web request with a length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the storage account.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param listingContext
     *            A {@link ListingContext} object that specifies parameters for
     *            the listing operation, if any. May be null.
     * @param detailsIncluded
     *            A {@link QueueListingDetails} object that specifies additional
     *            details to return with the listing, if any. May be null.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection list(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final ListingContext listingContext,
            final QueueListingDetails detailsIncluded) throws URISyntaxException, IOException, StorageException {
        final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext);

        if (detailsIncluded == QueueListingDetails.ALL || detailsIncluded == QueueListingDetails.METADATA) {
            builder.add(Constants.QueryConstants.INCLUDE, Constants.QueryConstants.METADATA);
        }

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setRequestMethod(Constants.HTTP_GET);

        return request;
    }

    /**
     * Constructs a web request to retrieve a specified number of messages from
     * the front of the queue without changing their visibility. Sign the web
     * request with a length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param numberOfMessages
     *            A nonzero value that specifies the number of messages to
     *            retrieve from the queue, up to a maximum of 32.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection peekMessages(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final int numberOfMessages) throws URISyntaxException, IOException,
            StorageException {

        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(PEEK_ONLY, "true");

        if (numberOfMessages != 0) {
            builder.add(NUMBER_OF_MESSAGES, Integer.toString(numberOfMessages));
        }

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setRequestMethod(Constants.HTTP_GET);

        return request;
    }

    /**
     * Constructs a web request to add a message to the back of the queue. Write the encoded message request body
     * generated with a call to QueueMessageSerializer#generateMessageRequestBody(String)} to the output
     * stream of the request. Sign the web request with the length of the encoded message request body.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param visibilityTimeoutInSeconds
     *            Specifies the length of time for the message to be invisible
     *            in seconds, starting when it is added to the queue. A value of
     *            0 will make the message visible immediately. The value must be
     *            greater than or equal to 0, and cannot be larger than 7 days.
     *            The visibility timeout of a message cannot be set to a value
     *            greater than the time-to-live time.
     * @param timeToLiveInSeconds
     *            Specifies the time-to-live interval for the message, in
     *            seconds. The maximum time-to-live allowed is 7 days. If this
     *            parameter is 0, the default time-to-live of 7 days is used.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection putMessage(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final int visibilityTimeoutInSeconds, final int timeToLiveInSeconds)
            throws IOException, URISyntaxException, StorageException {

        final UriQueryBuilder builder = new UriQueryBuilder();

        if (visibilityTimeoutInSeconds != 0) {
            builder.add(VISIBILITY_TIMEOUT, Integer.toString(visibilityTimeoutInSeconds));
        }

        if (timeToLiveInSeconds != 0) {
            builder.add(MESSAGE_TTL, Integer.toString(timeToLiveInSeconds));
        }

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setDoOutput(true);
        request.setRequestMethod(Constants.HTTP_POST);

        return request;
    }

    /**
     * Constructs a web request to retrieve messages from the front of the
     * queue. Sign the web request with a length of -1L.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param numberOfMessages
     *            A nonzero value that specifies the number of messages to
     *            retrieve from the queue, up to a maximum of 32.
     * @param visibilityTimeoutInSeconds
     *            Specifies the visibility timeout value in seconds, relative to
     *            server time, to make the retrieved messages invisible until
     *            the visibility timeout expires. The value must be larger than
     *            or equal to 0, and cannot be larger than 7 days. The
     *            visibility timeout of a message can be set to a value later
     *            than the expiry time, which will prevent the message from
     *            being retrieved again whether it is processed or not.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection retrieveMessages(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final int numberOfMessages, final int visibilityTimeoutInSeconds)
            throws URISyntaxException, IOException, StorageException {

        final UriQueryBuilder builder = new UriQueryBuilder();

        if (numberOfMessages != 0) {
            builder.add(NUMBER_OF_MESSAGES, Integer.toString(numberOfMessages));
        }

        builder.add(VISIBILITY_TIMEOUT, Integer.toString(visibilityTimeoutInSeconds));

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setRequestMethod("GET");

        return request;
    }

    /**
     * Constructs a web request to set user-defined metadata for the queue. Each
     * call to this operation replaces all existing metadata attached to the
     * queue. Use the {@link #addMetadata} method to specify the metadata to set
     * on the queue. To remove all metadata from the queue, call this web
     * request with no metadata added. Sign the web request with a length of 0.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection setMetadata(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        return BaseRequest.setMetadata(uri, queueOptions, null, opContext);
    }

    /**
     * Constructs a web request to update the visibility timeout of a message in
     * the queue. Optionally updates the message content if a message request
     * body is written to the output stream of the web request. The web request
     * should be signed with the length of the encoded message request body if
     * one is included, or a length of 0 if no message request body is included.
     *
     * @param uri
     *            A URI object that specifies the absolute URI to
     *            the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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.
     * @param popReceipt
     *            A String that contains the pop receipt value
     *            returned from an earlier call to {@link CloudQueueMessage#getPopReceipt} for the message to
     *            update.
     * @param visibilityTimeoutInSeconds
     *            Specifies the new visibility timeout value in seconds,
     *            relative to server time, to make the retrieved messages
     *            invisible until the visibility timeout expires. The value must
     *            be larger than or equal to 0, and cannot be larger than 7
     *            days. The visibility timeout of a message can be set to a
     *            value later than the expiry time, which will prevent the
     *            message from being retrieved again whether it is processed or
     *            not.
     * @return An HttpURLConnection configured for the specified
     *         operation.
     *
     * @throws IOException
     * @throws URISyntaxException
     *             If the URI is not valid.
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static HttpURLConnection updateMessage(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext, final String popReceipt, final int visibilityTimeoutInSeconds)
            throws URISyntaxException, IOException, StorageException {

        final UriQueryBuilder builder = new UriQueryBuilder();

        builder.add(POP_RECEIPT, popReceipt);

        builder.add(VISIBILITY_TIMEOUT, Integer.toString(visibilityTimeoutInSeconds));

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setDoOutput(true);
        request.setRequestMethod(Constants.HTTP_PUT);

        return request;
    }

    /**
     * Sets the ACL for the queue. Sign with length of aclBytes.
     *
     * @param uri
     *            The absolute URI to the queue.
     * @param queueOptions
     *            A {@link QueueRequestOptions} 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 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 HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection setAcl(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL);

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setDoOutput(true);
        request.setRequestMethod(Constants.HTTP_PUT);

        return request;
    }

    /**
     * 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 queueOptions
     *            A {@link QueueRequestOptions} 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 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 HttpURLConnection configured for the operation.
     * @throws StorageException
     */
    public static HttpURLConnection getAcl(final URI uri, final QueueRequestOptions queueOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL);

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, queueOptions, builder, opContext);

        request.setRequestMethod(Constants.HTTP_GET);

        return request;
    }

    /**
     * Private Default Ctor.
     */
    private QueueRequest() {
        // No op
    }
}


/**
 * 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;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * RESERVED FOR INTERNAL USE. A class used to deserialize a service properties.
 */
final class ServicePropertiesHandler extends DefaultHandler {

    private final Stack<String> elementStack = new Stack<String>();
    private StringBuilder bld = new StringBuilder();

    private final ServiceProperties props = new ServiceProperties();

    private CorsRule rule = new CorsRule();
    private boolean retentionPolicyEnabled;
    private int retentionPolicyDays;

    /**
     * Parses the {@link ServiceProperties} from the given XML stream.
     *
     * @param stream
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
    public static ServiceProperties readServicePropertiesFromStream(final InputStream stream) throws SAXException,
            IOException, ParserConfigurationException {
        SAXParser saxParser = Utility.getSAXParser();
        ServicePropertiesHandler handler = new ServicePropertiesHandler();
        handler.props.setLogging(null);
        handler.props.setHourMetrics(null);
        handler.props.setMinuteMetrics(null);
        handler.props.setCors(null);
        saxParser.parse(stream, handler);

        return handler.props;
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        this.elementStack.push(localName);

        if (Constants.AnalyticsConstants.CORS_RULE_ELEMENT.equals(localName)) {
            this.rule = new CorsRule();
        }
        else if (Constants.AnalyticsConstants.LOGGING_ELEMENT.equals(localName)) {
            this.props.setLogging(new LoggingProperties());
        }
        else if (Constants.AnalyticsConstants.HOUR_METRICS_ELEMENT.equals(localName)) {
            this.props.setHourMetrics(new MetricsProperties());
        }
        else if (Constants.AnalyticsConstants.MINUTE_METRICS_ELEMENT.equals(localName)) {
            this.props.setMinuteMetrics(new MetricsProperties());
        }
        else if (Constants.AnalyticsConstants.CORS_ELEMENT.equals(localName)) {
            this.props.setCors(new CorsProperties());
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        String currentNode = this.elementStack.pop();

        // if the node popped from the stack and the localName don't match, the xml document is improperly formatted
        if (!localName.equals(currentNode)) {
            throw new SAXException(SR.INVALID_RESPONSE_RECEIVED);
        }

        String parentNode = null;
        if (!this.elementStack.isEmpty()) {
            parentNode = this.elementStack.peek();
        }

        String value = this.bld.toString();
        if (value.isEmpty()) {
            value = null;
        }

        if (this.retentionPolicyEnabled && Constants.AnalyticsConstants.RETENTION_POLICY_ELEMENT.equals(currentNode)) {
            if (Constants.AnalyticsConstants.LOGGING_ELEMENT.equals(parentNode)) {
                this.props.getLogging().setRetentionIntervalInDays(this.retentionPolicyDays);
            }
            else if (Constants.AnalyticsConstants.HOUR_METRICS_ELEMENT.equals(parentNode)) {
                this.props.getHourMetrics().setRetentionIntervalInDays(this.retentionPolicyDays);
            }
            else if (Constants.AnalyticsConstants.MINUTE_METRICS_ELEMENT.equals(parentNode)) {
                this.props.getMinuteMetrics().setRetentionIntervalInDays(this.retentionPolicyDays);
            }
        }
        else if (Constants.AnalyticsConstants.CORS_RULE_ELEMENT.equals(currentNode)) {
            this.props.getCors().getCorsRules().add(this.rule);
        }
        else if (Constants.AnalyticsConstants.RETENTION_POLICY_ELEMENT.equals(parentNode)) {
            if (Constants.AnalyticsConstants.DAYS_ELEMENT.equals(currentNode)) {
                this.retentionPolicyDays = Integer.parseInt(value);
            }
            else if (Constants.AnalyticsConstants.ENABLED_ELEMENT.equals(currentNode)) {
                this.retentionPolicyEnabled = Boolean.parseBoolean(value);
            }
        }
        else if (Constants.AnalyticsConstants.LOGGING_ELEMENT.equals(parentNode)) {
            if (Constants.AnalyticsConstants.VERSION_ELEMENT.equals(currentNode)) {
                this.props.getLogging().setVersion(value);
            }
            else if (Constants.AnalyticsConstants.DELETE_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value)) {
                    this.props.getLogging().getLogOperationTypes().add(LoggingOperations.DELETE);
                }
            }
            else if (Constants.AnalyticsConstants.READ_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value)) {
                    this.props.getLogging().getLogOperationTypes().add(LoggingOperations.READ);
                }
            }
            else if (Constants.AnalyticsConstants.WRITE_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value)) {
                    this.props.getLogging().getLogOperationTypes().add(LoggingOperations.WRITE);
                }
            }
        }
        else if (Constants.AnalyticsConstants.HOUR_METRICS_ELEMENT.equals(parentNode)) {
            MetricsProperties metrics = this.props.getHourMetrics();
            if (Constants.AnalyticsConstants.VERSION_ELEMENT.equals(currentNode)) {
                metrics.setVersion(value);
            }
            else if (Constants.AnalyticsConstants.ENABLED_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value)) {
                    metrics.setMetricsLevel(metrics.getMetricsLevel() != MetricsLevel.SERVICE_AND_API ? MetricsLevel.SERVICE
                            : MetricsLevel.SERVICE_AND_API);
                }
            }
            else if (Constants.AnalyticsConstants.INCLUDE_APIS_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value) && metrics.getMetricsLevel() != MetricsLevel.DISABLED) {
                    metrics.setMetricsLevel(MetricsLevel.SERVICE_AND_API);
                }
            }
        }
        else if (Constants.AnalyticsConstants.MINUTE_METRICS_ELEMENT.equals(parentNode)) {
            MetricsProperties metrics = this.props.getMinuteMetrics();
            if (Constants.AnalyticsConstants.VERSION_ELEMENT.equals(currentNode)) {
                metrics.setVersion(value);
            }
            else if (Constants.AnalyticsConstants.ENABLED_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value)) {
                    metrics.setMetricsLevel(metrics.getMetricsLevel() != MetricsLevel.SERVICE_AND_API ? MetricsLevel.SERVICE
                            : MetricsLevel.SERVICE_AND_API);
                }
            }
            else if (Constants.AnalyticsConstants.INCLUDE_APIS_ELEMENT.equals(currentNode)) {
                if (Boolean.parseBoolean(value) && metrics.getMetricsLevel() != MetricsLevel.DISABLED) {
                    metrics.setMetricsLevel(MetricsLevel.SERVICE_AND_API);
                }
            }
        }
        else if (Constants.AnalyticsConstants.DEFAULT_SERVICE_VERSION.equals(currentNode)) {
            this.props.setDefaultServiceVersion(value);
        }
        else if (Constants.AnalyticsConstants.CORS_RULE_ELEMENT.equals(parentNode)) {
            if (Constants.AnalyticsConstants.ALLOWED_ORIGINS_ELEMENT.equals(currentNode)) {
                if (value != null) {
                    this.rule.setAllowedOrigins(splitToList(value, ","));
                }
            }
            else if (Constants.AnalyticsConstants.ALLOWED_METHODS_ELEMENT.equals(currentNode)) {
                if (value != null) {
                    this.rule.setAllowedMethods(splitToEnumSet(value, ","));
                }
            }
            else if (Constants.AnalyticsConstants.EXPOSED_HEADERS_ELEMENT.equals(currentNode)) {
                if (value != null) {
                    this.rule.setExposedHeaders(splitToList(value, ","));
                }
            }
            else if (Constants.AnalyticsConstants.ALLOWED_HEADERS_ELEMENT.equals(currentNode)) {
                if (value != null) {
                    this.rule.setAllowedHeaders(splitToList(value, ","));
                }
            }
            else if (Constants.AnalyticsConstants.MAX_AGE_IN_SECONDS_ELEMENT.equals(currentNode)) {
                this.rule.setMaxAgeInSeconds(Integer.parseInt(value));
            }
        }

        this.bld = new StringBuilder();
    }

    @Override
    public void characters(char ch[], int start, int length) throws SAXException {
        this.bld.append(ch, start, length);
    }

    /**
     * Split a delimiter separated string into an ArrayList
     *
     * @param str
     *            the string to split
     * @param delimiter
     *            the character to split on
     * @return the list of strings
     */
    private static List<String> splitToList(String str, String delimiter) {
        ArrayList<String> list = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(str, delimiter);
        while (st.hasMoreElements()) {
            list.add(st.nextToken());
        }
        return list;
    }

    /**
     * Split a delimiter separated string into a EnumSet of CorsHttpMethods
     *
     * @param str
     *            the string to split
     * @param delimiter
     *            the character to split on
     * @return the set of CorsHttpMethods
     */
    private static EnumSet<CorsHttpMethods> splitToEnumSet(String str, String delimiter) {
        EnumSet<CorsHttpMethods> set = EnumSet.noneOf(CorsHttpMethods.class);
        StringTokenizer st = new StringTokenizer(str, delimiter);
        while (st.hasMoreElements()) {
            set.add(CorsHttpMethods.valueOf(st.nextToken()));
        }
        return set;
    }
}


/**
 * 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;

import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.microsoft.azure.storage.core.Base64;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * Represents storage account credentials, based on storage account and access key, for accessing the Microsoft Azure
 * storage services.
 */
public final class StorageCredentialsAccountAndKey extends StorageCredentials {

    /**
     * Stores the Account name for the credentials.
     */
    private String accountName;

    /**
     * Stores a reference to the hmacsha256 Mac.
     */
    private Mac hmacSha256;

    /**
     * Stores the key.
     */
    private byte[] key;

    /**
     * Creates an instance of the StorageCredentialsAccountAndKey class, using the specified storage
     * account name and access key; the specified access key is in the form of a byte array.
     *
     * @param accountName
     *            A String that represents the name of the storage account.
     * @param key
     *            An array of bytes that represent the account access key.
     */
    public StorageCredentialsAccountAndKey(final String accountName, final byte[] key) {
        if (Utility.isNullOrEmptyOrWhitespace(accountName)) {
            throw new IllegalArgumentException(SR.INVALID_ACCOUNT_NAME);
        }

        if (key == null || key.length == 0) {
            throw new IllegalArgumentException(SR.INVALID_KEY);
        }

        this.accountName = accountName;
        this.key = key;
    }

    /**
     * Creates an instance of the StorageCredentialsAccountAndKey class, using the specified storage
     * account name and access key; the specified access key is stored as a String.
     *
     * @param accountName
     *            A String that represents the name of the storage account.
     * @param key
     *            A String that represents the Base-64-encoded account access key.
     */
    public StorageCredentialsAccountAndKey(final String accountName, final String key) {
        this(accountName, Base64.decode(key));
    }

    /**
     * Gets the account name.
     *
     * @return A String that contains the account name.
     */
    @Override
    public String getAccountName() {
        return this.accountName;
    }

    /**
     * Exports the value of the access key to a Base64-encoded string.
     *
     * @return A String that represents the Base64-encoded access key.
     */
    public String exportBase64EncodedKey() {
        return Base64.encode(this.key);
    }

    /**
     * Exports the value of the access key to an array of bytes.
     *
     * @return A byte array that represents the access key.
     */
    public byte[] exportKey() {
        final byte[] copy = this.key.clone();
        return copy;
    }

    /**
     * Sets the account name.
     *
     * @param accountName
     *          A String that contains the account name.
     */
    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    /**
     * Sets the name of the access key to be used when signing the request.
     *
     * @param key
     *        A String that represents the name of the access key to be used when signing the request.
     */
    public synchronized void updateKey(final String key) {
        this.updateKey(Base64.decode(key));
    }

    /**
     * Sets the name of the access key to be used when signing the request.
     *
     * @param key
     *        A String that represents the name of the access key to be used when signing the request.
     */
    public synchronized void updateKey(final byte[] key) {
        if (key == null || key.length == 0) {
            throw new IllegalArgumentException(SR.INVALID_KEY);
        }

        this.key = key;
        this.hmacSha256 = null;
    }

    /**
     * Returns a String that represents this instance, optionally including sensitive data.
     *
     * @param exportSecrets
     *            true to include sensitive data in the return string; otherwise, false.
     *
     * @return A String that represents this object, optionally including sensitive data.
     */
    @Override
    public String toString(final boolean exportSecrets) {
        return String.format("%s=%s;%s=%s", CloudStorageAccount.ACCOUNT_NAME_NAME, this.getAccountName(),
                CloudStorageAccount.ACCOUNT_KEY_NAME, exportSecrets ? this.exportBase64EncodedKey()
                        : "[key hidden]");
    }

    @Override
    public URI transformUri(URI resourceUri, OperationContext opContext) {
        return resourceUri;
    }

    @Override
    public StorageUri transformUri(StorageUri resourceUri, OperationContext opContext) {
        return resourceUri;
    }

    /**
     * Gets the HmacSha256 associated with the account key.
     *
     * @return A MAC created with the account key.
     *
     * @throws InvalidKeyException
     *             If the key is not a valid storage key.
     */
    public synchronized Mac getHmac256() throws InvalidKeyException {
        if (this.hmacSha256 == null) {
            // Initializes the HMAC-SHA256 Mac and SecretKey.
            try {
                this.hmacSha256 = Mac.getInstance("HmacSHA256");
            }
            catch (final NoSuchAlgorithmException e) {
                throw new IllegalArgumentException();
            }
            this.hmacSha256.init(new SecretKeySpec(this.key, "HmacSHA256"));
        }
        return this.hmacSha256;
    }
}