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

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.Map;

import com.microsoft.azure.storage.AccessCondition;
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;
import com.microsoft.azure.storage.core.Utility;

/**
 * RESERVED FOR INTERNAL USE. Provides a set of methods for constructing requests for file operations.
 */
final class FileRequest {

    private static final String RANGE_QUERY_ELEMENT_NAME = "range";

    private static final String RANGE_LIST_QUERY_ELEMENT_NAME = "rangelist";

    private static final String SNAPSHOTS_QUERY_ELEMENT_NAME = "snapshots";

    /**
     * Generates a web request to abort a copy operation.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            The access condition to apply to the request. Only lease conditions are supported for this operation.
     * @param copyId
     *            A String object that identifying the copy operation.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     *             An exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     * @throws IOException
     * @throws URISyntaxException
     */
    public static HttpURLConnection abortCopy(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final String copyId)
            throws StorageException, IOException, URISyntaxException {

        final UriQueryBuilder builder = new UriQueryBuilder();

        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.COPY);
        builder.add(Constants.QueryConstants.COPY_ID, copyId);

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

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

        request.setRequestProperty(Constants.HeaderConstants.COPY_ACTION_HEADER,
                Constants.HeaderConstants.COPY_ACTION_ABORT);

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Adds the metadata.
     *
     * @param request
     *            The request.
     * @param metadata
     *            The metadata.
     */
    public static void addMetadata(final HttpURLConnection request, final Map<String, String> metadata,
            final OperationContext opContext) {
        BaseRequest.addMetadata(request, metadata, opContext);
    }

    /**
     * Adds the properties.
     *
     * @param request
     *            The request
     * @param properties
     *            The file properties
     */
    private static void addProperties(final HttpURLConnection request, FileProperties properties) {
        BaseRequest.addOptionalHeader(request, FileConstants.CACHE_CONTROL_HEADER, properties.getCacheControl());
        BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_DISPOSITION_HEADER,
                properties.getContentDisposition());
        BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_ENCODING_HEADER, properties.getContentEncoding());
        BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_LANGUAGE_HEADER, properties.getContentLanguage());
        BaseRequest.addOptionalHeader(request, FileConstants.FILE_CONTENT_MD5_HEADER, properties.getContentMD5());
        BaseRequest.addOptionalHeader(request, FileConstants.CONTENT_TYPE_HEADER, properties.getContentType());
    }

    /**
     * Adds the share snapshot if present.
     * Only for listing files and directories which requires a different query param.
     *
     * @param builder
     *            a query builder.
     * @param snapshotVersion
     *            the share snapshot version to the query builder.
     * @throws StorageException
     */
    public static void addShareSnapshot(final UriQueryBuilder builder, final String snapshotVersion)
            throws StorageException {
        if (snapshotVersion != null) {
            builder.add(Constants.QueryConstants.SHARE_SNAPSHOT, snapshotVersion);
        }
    }

    /**
     * Creates a request to copy a file, Sign with 0 length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param source
     *            The canonical path to the source file,
     *            in the form /<account-name>/<share-name>/<directory-path>/<file-name>.
     * @param sourceAccessConditionType
     *            A type of condition to check on the source file.
     * @param sourceAccessConditionValue
     *            The value of the condition to check on the source file
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     * @throws IOException
     * @throws URISyntaxException
     */
    public static HttpURLConnection copyFrom(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, String source)
            throws StorageException, IOException, URISyntaxException {

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, null, opContext);

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

        request.setRequestProperty(Constants.HeaderConstants.COPY_SOURCE_HEADER, source);

        if (sourceAccessCondition != null) {
            sourceAccessCondition.applyConditionToRequest(request);
        }

        if (destinationAccessCondition != null) {
            destinationAccessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Adds the properties.
     *
     * @param request
     *            The request
     * @param properties
     *            The share properties
     */
    private static void addProperties(final HttpURLConnection request, FileShareProperties properties) {
        final Integer shareQuota = properties.getShareQuota();
        BaseRequest.addOptionalHeader(
                request, FileConstants.SHARE_QUOTA_HEADER, shareQuota == null ? null : shareQuota.toString());
    }

    /**
     * Constructs a web request to create a new share. Sign with 0 length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param properties
     *            The properties to set for the share.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection createShare(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final FileShareProperties properties)
            throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder shareBuilder = getShareUriQueryBuilder();
        final HttpURLConnection request = BaseRequest.create(uri, fileOptions, shareBuilder, opContext);
        addProperties(request, properties);
        return request;
    }

    /**
     * Constructs a HttpURLConnection to delete the file, Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection deleteFile(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        final HttpURLConnection request = BaseRequest.delete(uri, fileOptions, builder, opContext);
        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a web request to delete the share and all of the directories and files within it. Sign with no length
     * specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection deleteShare(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, String snapshotVersion, DeleteShareSnapshotsOption deleteSnapshotsOption)
                    throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder shareBuilder = getShareUriQueryBuilder();
        FileRequest.addShareSnapshot(shareBuilder, snapshotVersion);
        HttpURLConnection request = BaseRequest.delete(uri, fileOptions, shareBuilder, opContext);
        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        switch (deleteSnapshotsOption) {
        case NONE:
            // nop
            break;
        case INCLUDE_SNAPSHOTS:
            request.setRequestProperty(Constants.HeaderConstants.DELETE_SNAPSHOT_HEADER,
                    Constants.HeaderConstants.INCLUDE_SNAPSHOTS_VALUE);
            break;
        default:
            break;
        }

        return request;
    }

    /**
     * Constructs a web request to return the ACL for this share. Sign with no length specified.
     *
     * @param uri
     *            The absolute URI to the share.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 CloudFileClient}.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @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 FileRequestOptions fileOptions,
            final AccessCondition accessCondition, final OperationContext opContext) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder builder = getShareUriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL);

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

        request.setRequestMethod(Constants.HTTP_GET);

        if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) {
            accessCondition.applyLeaseConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to download the file, Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param snapshotVersion
     *            The snapshot version, if the share is a snapshot.
     * @param offset
     *            The offset at which to begin returning content.
     * @param count
     *            The number of bytes to return.
     * @param requestRangeContentMD5
     *            If set to true, request an MD5 header for the specified range.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection getFile(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion, final Long offset,
            final Long count, boolean requestRangeContentMD5) throws IOException, URISyntaxException, StorageException {

        if (offset != null && requestRangeContentMD5) {
            Utility.assertNotNull("count", count);
            Utility.assertInBounds("count", count, 1, Constants.MAX_BLOCK_SIZE);
        }

        final UriQueryBuilder builder = new UriQueryBuilder();
        FileRequest.addShareSnapshot(builder, snapshotVersion);
        final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext);
        request.setRequestMethod(Constants.HTTP_GET);

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        if (offset != null) {
            long rangeStart = offset;
            long rangeEnd;
            if (count != null) {
                rangeEnd = offset + count - 1;
                request.setRequestProperty(Constants.HeaderConstants.STORAGE_RANGE_HEADER, String.format(
                        Utility.LOCALE_US, Constants.HeaderConstants.RANGE_HEADER_FORMAT, rangeStart, rangeEnd));
            }
            else {
                request.setRequestProperty(Constants.HeaderConstants.STORAGE_RANGE_HEADER, String.format(
                        Utility.LOCALE_US, Constants.HeaderConstants.BEGIN_RANGE_HEADER_FORMAT, rangeStart));
            }
        }

        if (offset != null && requestRangeContentMD5) {
            request.setRequestProperty(Constants.HeaderConstants.RANGE_GET_CONTENT_MD5, Constants.TRUE);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to return the file's system properties, Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @return a HttpURLConnection to use to perform the operation.
     * @param snapshotVersion
     *            the snapshot version to the query builder.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection getFileProperties(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException,
            IOException, URISyntaxException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        return getProperties(uri, fileOptions, opContext, accessCondition, builder, snapshotVersion);
    }

    /**
     * Constructs a HttpURLConnection to return a list of the file's file ranges. Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection getFileRanges(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final String snapshotVersion) throws StorageException,
            IOException, URISyntaxException {

        final UriQueryBuilder builder = new UriQueryBuilder();
        addShareSnapshot(builder, snapshotVersion);
        builder.add(Constants.QueryConstants.COMPONENT, RANGE_LIST_QUERY_ELEMENT_NAME);

        final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext);
        request.setRequestMethod(Constants.HTTP_GET);

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a web request to return the user-defined metadata for this share. Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param snapshotVersion
     *            the snapshot version to the query builder.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection getShareProperties(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, AccessCondition accessCondition, final String snapshotVersion) throws IOException, URISyntaxException,
            StorageException {
        final UriQueryBuilder shareBuilder = getShareUriQueryBuilder();

        return getProperties(uri, fileOptions, opContext, accessCondition, shareBuilder, snapshotVersion);
    }

    /**
     * Constructs a web request to return the stats, such as usage, for this share. Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param options
     *            A {@link FileRequestOptions} 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 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 HttpURLConnection configured for the operation.
     * @throws StorageException
     */
    public static HttpURLConnection getShareStats(final URI uri, final FileRequestOptions options,
            final OperationContext opContext)
            throws IOException, URISyntaxException, StorageException {
        UriQueryBuilder shareBuilder = getShareUriQueryBuilder();
        shareBuilder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.STATS);

        final HttpURLConnection retConnection = BaseRequest.createURLConnection(uri, options, shareBuilder, opContext);
        retConnection.setRequestMethod(Constants.HTTP_GET);

        return retConnection;
    }

    /**
     * Gets the share Uri query builder.
     *
     * A UriQueryBuilder for the share.
     *
     * @throws StorageException
     */
    private static UriQueryBuilder getShareUriQueryBuilder() throws StorageException {
        final UriQueryBuilder uriBuilder = new UriQueryBuilder();
        try {
            uriBuilder.add(Constants.QueryConstants.RESOURCETYPE, "share");
        }
        catch (final IllegalArgumentException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
        return uriBuilder;
    }

    /**
     * Gets the share Uri query builder.
     *
     * A UriQueryBuilder for the share.
     *
     * @throws StorageException
     */
    private static UriQueryBuilder getDirectoryUriQueryBuilder() throws StorageException {
        final UriQueryBuilder uriBuilder = new UriQueryBuilder();
        try {
            uriBuilder.add(Constants.QueryConstants.RESOURCETYPE, "directory");
        }
        catch (final IllegalArgumentException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
        return uriBuilder;
    }

    /**
     * Constructs a web request to return the user-defined metadata. Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @param snapshotVersion
     *            the snapshot version to the query builder.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    private static HttpURLConnection getProperties(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, AccessCondition accessCondition, final UriQueryBuilder builder,
            String snapshotVersion)
            throws IOException, URISyntaxException, StorageException {
        addShareSnapshot(builder, snapshotVersion);
        HttpURLConnection request = BaseRequest.getProperties(uri, fileOptions, builder, opContext);

        if (accessCondition != null) {
            accessCondition.applyLeaseConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a request to return a listing of all shares in this storage account. Sign with no length
     * specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param listingContext
     *            A set of parameters for the listing operation.
     * @param detailsIncluded
     *            A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate
     *            whether share snapshots and/or metadata will be returned.
     * @return a HttpURLConnection configured for the operation.
     * @throws IOException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection listShares(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final ListingContext listingContext,
            final EnumSet<ShareListingDetails> detailsIncluded) throws URISyntaxException, IOException, StorageException {
        final UriQueryBuilder builder = BaseRequest.getListUriQueryBuilder(listingContext);

        if (detailsIncluded != null && detailsIncluded.size() > 0) {
            final StringBuilder sb = new StringBuilder();
            boolean started = false;

            if (detailsIncluded.contains(ShareListingDetails.SNAPSHOTS)) {
                started = true;
                sb.append(SNAPSHOTS_QUERY_ELEMENT_NAME);
            }

            if (detailsIncluded.contains(ShareListingDetails.METADATA)) {
                if (started)
                {
                    sb.append(",");
                }

                sb.append(Constants.QueryConstants.METADATA);
            }

            builder.add(Constants.QueryConstants.INCLUDE, sb.toString());
        }

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

        request.setRequestMethod(Constants.HTTP_GET);

        return request;
    }

    /**
     * Constructs a web request to set user-defined metadata for the share, Sign with 0 Length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection setShareMetadata(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder shareBuilder = getShareUriQueryBuilder();
        return setMetadata(uri, fileOptions, opContext, accessCondition, shareBuilder);
    }

    /**
     * Constructs a web request to set user-defined metadata for the directory, Sign with 0 Length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @return a HttpURLConnection configured for the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * */
    public static HttpURLConnection setDirectoryMetadata(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder();
        return setMetadata(uri, fileOptions, opContext, accessCondition, directoryBuilder);
    }

    /**
     * Constructs a web request to create a new directory. Sign with 0 length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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 HttpURLConnection configured for the operation.
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection createDirectory(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder();
        return BaseRequest.create(uri, fileOptions, directoryBuilder, opContext);
    }

    /**
     * Constructs a web request to delete the directory and all of the directories and files within it. Sign with no
     * length
     * specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection deleteDirectory(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder();
        HttpURLConnection request = BaseRequest.delete(uri, fileOptions, directoryBuilder, opContext);
        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a web request to return the properties for this directory. Sign with no length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the directory.
     * @param snapshotVersion
     *            the snapshot version to the query builder.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection getDirectoryProperties(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, AccessCondition accessCondition, String snapshotVersion) throws IOException, URISyntaxException,
            StorageException {
        final UriQueryBuilder directoryBuilder = getDirectoryUriQueryBuilder();
        return getProperties(uri, fileOptions, opContext, accessCondition, directoryBuilder, snapshotVersion);
    }

    /**
     * Constructs a request to return a listing of all files and directories in this storage account. Sign with no
     * length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param listingContext
     *            A set of parameters for the listing operation.
     * @param snapshotVersion
     *            the snapshot version to the query builder.
     * @return a HttpURLConnection configured for the operation.
     * @throws IOException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection listFilesAndDirectories(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final ListingContext listingContext, String snapshotVersion) throws URISyntaxException,
            IOException, StorageException {

        final UriQueryBuilder builder = getDirectoryUriQueryBuilder();
        addShareSnapshot(builder, snapshotVersion);
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST);

        if (listingContext != null) {
            if (!Utility.isNullOrEmpty(listingContext.getMarker())) {
                builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker());
            }

            if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) {
                builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString());
            }

            if (!Utility.isNullOrEmpty(listingContext.getPrefix())) {
                builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix().toString());
            }
        }

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

        request.setRequestMethod(Constants.HTTP_GET);

        return request;
    }

    /**
     * Constructs a HttpURLConnection to upload a file.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param properties
     *            The properties to set for the file.
     * @param fileSize
     *            The size of the file.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection putFile(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final FileProperties properties,
            final long fileSize) throws IOException, URISyntaxException, StorageException {
        final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, null, opContext);

        request.setDoOutput(true);

        request.setRequestMethod(Constants.HTTP_PUT);

        addProperties(request, properties);

        request.setFixedLengthStreamingMode(0);
        request.setRequestProperty(Constants.HeaderConstants.CONTENT_LENGTH, "0");

        request.setRequestProperty(FileConstants.FILE_TYPE_HEADER, FileConstants.FILE);
        request.setRequestProperty(FileConstants.CONTENT_LENGTH_HEADER, String.valueOf(fileSize));

        properties.setLength(fileSize);

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to upload a file range. Sign with file size for update, or 0 for clear.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param range
     *            a {link @FileRange} representing the file range
     * @param operationType
     *            a {link @FileRangeOperationType} enumeration value representing the file range operation type.
     *
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection putRange(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final FileRange range,
            FileRangeOperationType operationType) throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, RANGE_QUERY_ELEMENT_NAME);

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

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

        if (operationType == FileRangeOperationType.CLEAR) {
            request.setFixedLengthStreamingMode(0);
        }

        // Range write is either update or clear; required
        request.setRequestProperty(FileConstants.FILE_RANGE_WRITE, operationType.toString());
        request.setRequestProperty(Constants.HeaderConstants.STORAGE_RANGE_HEADER, range.toString());

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to set the share's properties, signed with zero length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param options
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param properties
     *            The properties to upload.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection setShareProperties(final URI uri, final FileRequestOptions options,
            final OperationContext opContext, final AccessCondition accessCondition, final FileShareProperties properties)
            throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = getShareUriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);

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

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

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        if (properties != null) {
            addProperties(request, properties);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to set the file's size, Sign with zero length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param newFileSize
     *            The new file size. Set this parameter to null to keep the existing file size.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection resize(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final Long newFileSize)
            throws IOException, URISyntaxException, StorageException {
        final HttpURLConnection request = setFileProperties(uri, fileOptions, opContext, accessCondition, null);

        if (newFileSize != null) {
            request.setRequestProperty(FileConstants.CONTENT_LENGTH_HEADER, newFileSize.toString());
        }

        return request;
    }

    /**
     * Sets the ACL for the share. Sign with length of aclBytes.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection setAcl(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition)
            throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = getShareUriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.ACL);

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

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

        if (accessCondition != null && !Utility.isNullOrEmpty(accessCondition.getLeaseID())) {
            accessCondition.applyLeaseConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to set metadata, Sign with 0 length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    private static HttpURLConnection setMetadata(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final UriQueryBuilder builder)
            throws IOException, URISyntaxException, StorageException {
        final HttpURLConnection request = BaseRequest.setMetadata(uri, fileOptions, builder, opContext);

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to set the file's metadata, Sign with 0 length.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection setFileMetadata(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        return setMetadata(uri, fileOptions, opContext, accessCondition, null);
    }

    /**
     * Constructs a HttpURLConnection to create a snapshot of the share.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the share.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection snapshotShare(final URI uri, final FileRequestOptions fileOptions,
                                                  final OperationContext opContext, final AccessCondition accessCondition) throws IOException,
            URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.RESOURCETYPE, "share");
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.SNAPSHOT);
        final HttpURLConnection request = BaseRequest.createURLConnection(uri, fileOptions, builder, opContext);

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

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        return request;
    }

    /**
     * Constructs a HttpURLConnection to set the file's properties, Sign with zero length specified.
     *
     * @param uri
     *            A java.net.URI object that specifies the absolute URI.
     * @param fileOptions
     *            A {@link FileRequestOptions} 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 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.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the file.
     * @param properties
     *            The properties to upload.
     * @return a HttpURLConnection to use to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if the resource URI is invalid
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection setFileProperties(final URI uri, final FileRequestOptions fileOptions,
            final OperationContext opContext, final AccessCondition accessCondition, final FileProperties properties)
            throws IOException, URISyntaxException, StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);

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

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

        if (accessCondition != null) {
            accessCondition.applyConditionToRequest(request);
        }

        if (properties != null) {
            addProperties(request, properties);
        }

        return request;
    }

    /**
     * Private Default Ctor
     */
    private FileRequest() {
        // 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.file;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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.StorageErrorCode;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.Base64;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * Provides an input stream to read a given file resource.
 */
public class FileInputStream extends InputStream {
    /**
     * Holds the reference to the file this stream is associated with.
     */
    private final CloudFile parentFileRef;

    /**
     * Holds the reference to the MD5 digest for the file.
     */
    private MessageDigest md5Digest;

    /**
     * A flag to determine if the stream is faulted, if so the last error will be thrown on next operation.
     */
    private volatile boolean streamFaulted;

    /**
     * Holds the last exception this stream encountered.
     */
    private IOException lastError;

    /**
     * Holds the OperationContext for the current stream.
     */
    private final OperationContext opContext;

    /**
     * Holds the options for the current stream
     */
    private final FileRequestOptions options;

    /**
     * Holds the stream length.
     */
    private long streamLength = -1;

    /**
     * Holds the stream read size.
     */
    private final int readSize;

    /**
     * A flag indicating if the File MD5 should be validated.
     */
    private boolean validateFileMd5;

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

    /**
     * Holds the reference to the current buffered data.
     */
    private ByteArrayInputStream currentBuffer;

    /**
     * Holds an absolute byte position for the mark feature.
     */
    private long markedPosition;

    /**
     * Holds the mark delta for which the mark position is expired.
     */
    private int markExpiry;

    /**
     * Holds an absolute byte position of the current read position.
     */
    private long currentAbsoluteReadPosition;

    /**
     * Holds the absolute byte position of the start of the current buffer.
     */
    private long bufferStartOffset;

    /**
     * Holds the length of the current buffer in bytes.
     */
    private int bufferSize;

    /**
     * Holds the {@link AccessCondition} object that represents the access conditions for the file.
     */
    private AccessCondition accessCondition = null;

    /**
     * Initializes a new instance of the FileInputStream class.
     *
     * @param parentFile
     *            A {@link CloudFile} object which represents the file that this stream is associated with.
     * @param accessCondition
     *            An {@link AccessCondition} object which represents the access conditions for the file.
     * @param options
     *            A {@link FileRequestOptions} object which represents that specifies any additional options for the
     *            request.
     * @param opContext
     *            An {@link OperationContext} object which is used to track the execution of the operation.
     *
     * @throws StorageException
     *             An exception representing any error which occurred during the operation.
     */
    @DoesServiceRequest
    protected FileInputStream(final CloudFile parentFile, final AccessCondition accessCondition,
            final FileRequestOptions options, final OperationContext opContext) throws StorageException {
        this.parentFileRef = parentFile;
        this.options = new FileRequestOptions(options);
        this.opContext = opContext;
        this.streamFaulted = false;
        this.currentAbsoluteReadPosition = 0;
        this.readSize = parentFile.getStreamMinimumReadSizeInBytes();

        if (options.getUseTransactionalContentMD5() && this.readSize > 4 * Constants.MB) {
            throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER);
        }

        parentFile.downloadAttributes(accessCondition, this.options, this.opContext);

        this.retrievedContentMD5Value = parentFile.getProperties().getContentMD5();

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

        String previousLeaseId = null;
        if (accessCondition != null) {
            previousLeaseId = accessCondition.getLeaseID();
        }

        this.accessCondition = AccessCondition.generateIfMatchCondition(this.parentFileRef.getProperties().getEtag());
        this.accessCondition.setLeaseID(previousLeaseId);

        this.streamLength = parentFile.getProperties().getLength();

        if (this.validateFileMd5) {
            try {
                this.md5Digest = MessageDigest.getInstance("MD5");
            }
            catch (final NoSuchAlgorithmException e) {
                // This wont happen, throw fatal.
                throw Utility.generateNewUnexpectedStorageException(e);
            }
        }

        this.reposition(0);
    }

    /**
     * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream without
     * blocking by the next invocation of a method for this input stream. The next invocation might be the same thread
     * or another thread. A single read or skip of this many bytes will not block, but may read or skip fewer bytes.
     *
     * @return An int which represents an estimate of the number of bytes that can be read (or skipped
     *         over)
     *         from this input stream without blocking, or 0 when it reaches the end of the input stream.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @Override
    public synchronized int available() throws IOException {
        return this.bufferSize - (int) (this.currentAbsoluteReadPosition - this.bufferStartOffset);
    }

    /**
     * Helper function to check if the stream is faulted, if it is it surfaces the exception.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    private synchronized void checkStreamState() throws IOException {
        if (this.streamFaulted) {
            throw this.lastError;
        }
    }

    /**
     * Closes this input stream and releases any system resources associated with the stream.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @Override
    public synchronized void close() throws IOException {
        this.currentBuffer = null;
        this.streamFaulted = true;
        this.lastError = new IOException(SR.STREAM_CLOSED);
    }

    /**
     * Dispatches a read operation of N bytes.
     *
     * @param readLength
     *            An int which represents the number of bytes to read.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @DoesServiceRequest
    private synchronized void dispatchRead(final int readLength) throws IOException {
        try {
            final byte[] byteBuffer = new byte[readLength];

            this.parentFileRef.downloadRangeInternal(this.currentAbsoluteReadPosition, (long) readLength, byteBuffer,
                    0, null /* this.accessCondition */, this.options, this.opContext);

            // Check Etag manually for now -- use access condition once conditional headers supported.
            if (this.accessCondition != null) {
                if (!this.accessCondition.getIfMatch().equals(this.parentFileRef.getProperties().getEtag())) {
                    throw new StorageException(StorageErrorCode.CONDITION_FAILED.toString(),
                            SR.INVALID_CONDITIONAL_HEADERS, HttpURLConnection.HTTP_PRECON_FAILED, null, null);
                }
            }

            this.currentBuffer = new ByteArrayInputStream(byteBuffer);
            this.bufferSize = readLength;
            this.bufferStartOffset = this.currentAbsoluteReadPosition;
        }
        catch (final StorageException e) {
            this.streamFaulted = true;
            this.lastError = Utility.initIOException(e);
            throw this.lastError;
        }
    }

    /**
     * Marks the current position in this input stream. A subsequent call to the reset method repositions this stream at
     * the last marked position so that subsequent reads re-read the same bytes.
     *
     * @param readlimit
     *            An int which represents the maximum limit of bytes that can be read before the mark
     *            position becomes invalid.
     */
    @Override
    public synchronized void mark(final int readlimit) {
        this.markedPosition = this.currentAbsoluteReadPosition;
        this.markExpiry = readlimit;
    }

    /**
     * Tests if this input stream supports the mark and reset methods. Whether or not mark and reset are supported is an
     * invariant property of a particular input stream instance. The markSupported method of {@link InputStream} returns
     * false.
     *
     * @return True if this stream instance supports the mark and reset methods; False
     *         otherwise.
     */
    @Override
    public boolean markSupported() {
        return true;
    }

    /**
     * Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If
     * no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks
     * until input data is available, the end of the stream is detected, or an exception is thrown.
     *
     * @return An int which represents the total number of bytes read into the buffer, or -1 if
     *         there is no more data because the end of the stream has been reached.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @Override
    @DoesServiceRequest
    public int read() throws IOException {
        final byte[] tBuff = new byte[1];
        final int numberOfBytesRead = this.read(tBuff, 0, 1);

        if (numberOfBytesRead > 0) {
            return tBuff[0] & 0xFF;
        }
        else if (numberOfBytesRead == 0) {
            throw new IOException(SR.UNEXPECTED_STREAM_READ_ERROR);
        }
        else {
            return -1;
        }
    }

    /**
     * Reads some number of bytes from the input stream and stores them into the buffer array b. The number
     * of bytes actually read is returned as an integer. This method blocks until input data is available, end of file
     * is detected, or an exception is thrown. If the length of b is zero, then no bytes are read and 0 is
     * returned; otherwise, there is an attempt to read at least one byte. If no byte is available because the stream is
     * at the end of the file, the value -1 is returned; otherwise, at least one byte is read and stored into
     * b.
     *
     * The first byte read is stored into element b[0], the next one into b[1], and so on. The
     * number of bytes read is, at most, equal to the length of b. Let k be the number of
     * bytes actually read; these bytes will be stored in elements b[0] through b[k-1],
     * leaving elements b[k] through b[b.length-1] unaffected.
     *
     * The read(b) method for class {@link InputStream} has the same effect as:
     *
     * read(b, 0, b.length)
     *
     * @param b
     *            A byte array which represents the buffer into which the data is read.
     *
     * @throws IOException
     *             If the first byte cannot be read for any reason other than the end of the file, if the input stream
     *             has been closed, or if some other I/O error occurs.
     * @throws NullPointerException
     *             If the byte array b is null.
     */
    @Override
    @DoesServiceRequest
    public int read(final byte[] b) throws IOException {
        return this.read(b, 0, b.length);
    }

    /**
     * Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to
     * read as many as len bytes, but a smaller number may be read. The number of bytes actually read is
     * returned as an integer. This method blocks until input data is available, end of file is detected, or an
     * exception is thrown.
     *
     * If len is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at
     * least one byte. If no byte is available because the stream is at end of file, the value -1 is returned;
     * otherwise, at least one byte is read and stored into b.
     *
     * The first byte read is stored into element b[off], the next one into b[off+1], and so
     * on. The number of bytes read is, at most, equal to len. Let k be the number of bytes
     * actually read; these bytes will be stored in elements b[off] through b[off+k-1],
     * leaving elements b[off+k] through b[off+len-1] unaffected.
     *
     * In every case, elements b[0] through b[off] and elements b[off+len]
     * through b[b.length-1] are unaffected.
     *
     * The read(b, off, len) method for class {@link InputStream} simply calls the method
     * read() repeatedly. If the first such call results in an IOException, that exception is
     * returned from the call to the read(b, off, len) method. If any subsequent call to
     * read() results in a IOException, the exception is caught and treated
     * as if it were end of file; the bytes read up to that point are stored into b and the number of bytes
     * read before the exception occurred is returned. The default implementation of this method blocks until the
     * requested amount of input data len has been read, end of file is detected, or an exception is
     * thrown. Subclasses are encouraged to provide a more efficient implementation of this method.
     *
     * @param b
     *            A byte array which represents the buffer into which the data is read.
     * @param off
     *            An int which represents the start offset in the byte array at which the data
     *            is written.
     * @param len
     *            An int which represents the maximum number of bytes to read.
     *
     * @return An int which represents the total number of bytes read into the buffer, or -1 if
     *         there is no more data because the end of the stream has been reached.
     *
     * @throws IOException
     *             If the first byte cannot be read for any reason other than end of file, or if the input stream has
     *             been closed, or if some other I/O error occurs.
     * @throws NullPointerException
     *             If the byte array b is null.
     * @throws IndexOutOfBoundsException
     *             If off is negative, len is negative, or len is greater than
     *             b.length - off.
     */
    @Override
    @DoesServiceRequest
    public int read(final byte[] b, final int off, final int len) throws IOException {
        if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        return this.readInternal(b, off, len);
    }

    /**
     * Performs internal read to the given byte buffer.
     *
     * @param b
     *            A byte array which represents the buffer into which the data is read.
     * @param off
     *            An int which represents the start offset in the byte array b at
     *            which the data is written.
     * @param len
     *            An int which represents the maximum number of bytes to read.
     *
     * @return An int which represents the total number of bytes read into the buffer, or -1 if
     *         there is no more data because the end of the stream has been reached.
     *
     * @throws IOException
     *             If the first byte cannot be read for any reason other than end of file, or if the input stream has
     *             been closed, or if some other I/O error occurs.
     */
    @DoesServiceRequest
    private synchronized int readInternal(final byte[] b, final int off, int len) throws IOException {
        this.checkStreamState();

        // if buffer is empty do next get operation
        if ((this.currentBuffer == null || this.currentBuffer.available() == 0)
                && this.currentAbsoluteReadPosition < this.streamLength) {
            this.dispatchRead((int) Math.min(this.readSize, this.streamLength - this.currentAbsoluteReadPosition));
        }

        len = Math.min(len, this.readSize);

        // do read from buffer
        final int numberOfBytesRead = this.currentBuffer.read(b, off, len);

        if (numberOfBytesRead > 0) {
            this.currentAbsoluteReadPosition += numberOfBytesRead;

            if (this.validateFileMd5) {
                this.md5Digest.update(b, off, numberOfBytesRead);

                if (this.currentAbsoluteReadPosition == this.streamLength) {
                    // Reached end of stream, validate md5.
                    final String calculatedMd5 = Base64.encode(this.md5Digest.digest());
                    if (!calculatedMd5.equals(this.retrievedContentMD5Value)) {
                        this.lastError = Utility
                                .initIOException(new StorageException(
                                        StorageErrorCodeStrings.INVALID_MD5,
                                        String.format(
                                                "File data corrupted (integrity check failed), Expected value is %s, retrieved %s",
                                                this.retrievedContentMD5Value, calculatedMd5),
                                        Constants.HeaderConstants.HTTP_UNUSED_306, null, null));
                        this.streamFaulted = true;
                        throw this.lastError;
                    }
                }
            }
        }

        // update markers
        if (this.markExpiry > 0 && this.markedPosition + this.markExpiry < this.currentAbsoluteReadPosition) {
            this.markedPosition = 0;
            this.markExpiry = 0;
        }

        return numberOfBytesRead;
    }

    /**
     * Repositions the stream to the given absolute byte offset.
     *
     * @param absolutePosition
     *            A long which represents the absolute byte offset withitn the stream reposition.
     */
    private synchronized void reposition(final long absolutePosition) {
        this.currentAbsoluteReadPosition = absolutePosition;
        this.currentBuffer = new ByteArrayInputStream(new byte[0]);
    }

    /**
     * Repositions this stream to the position at the time the mark method was last called on this input stream. Note
     * repositioning the file read stream will disable file MD5 checking.
     *
     * @throws IOException
     *             If this stream has not been marked or if the mark has been invalidated.
     */
    @Override
    public synchronized void reset() throws IOException {
        if (this.markedPosition + this.markExpiry < this.currentAbsoluteReadPosition) {
            throw new IOException(SR.MARK_EXPIRED);
        }

        this.validateFileMd5 = false;
        this.md5Digest = null;
        this.reposition(this.markedPosition);
    }

    /**
     * Skips over and discards n bytes of data from this input stream. The skip method may, for a variety of reasons,
     * end up skipping over some smaller number of bytes, possibly 0. This may result from any of a number of
     * conditions; reaching end of file before n bytes have been skipped is only one possibility. The actual number of
     * bytes skipped is returned. If n is negative, no bytes are skipped.
     *
     * Note repositioning the file read stream will disable file MD5 checking.
     *
     * @param n
     *            A long which represents the number of bytes to skip.
     */
    @Override
    public synchronized long skip(final long n) throws IOException {
        if (n == 0) {
            return 0;
        }

        if (n < 0 || this.currentAbsoluteReadPosition + n > this.streamLength) {
            throw new IndexOutOfBoundsException();
        }

        this.validateFileMd5 = false;
        this.md5Digest = null;
        this.reposition(this.currentAbsoluteReadPosition + n);
        return n;
    }
}


package com.microsoft.azure.storage.samples;

import java.io.PrintWriter;
import java.io.StringWriter;

import com.microsoft.azure.storage.samples.blob.BlobGettingStartedTask;
import com.microsoft.azure.storage.samples.queue.QueueGettingStartedTask;
import com.microsoft.azure.storage.samples.table.TableGettingStartedTask;
import com.microsoft.azure.storage.samples.table.TablePayloadFormatTask;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class MainActivity extends Activity {

    /**
     * MODIFY THIS!
     *
     * Stores the storage connection string.
     * Only use Shared Key authentication (Account Key) for testing purposes!
     * Your account name and account key, which give full read/write access to the associated Storage account,
     * will be distributed to every person that downloads your app.
     * This is not a good practice as you risk having your key compromised by untrusted clients.
     * Please consult following documents to understand and use Shared Access Signatures instead.
     * https://docs.microsoft.com/en-us/rest/api/storageservices/delegating-access-with-a-shared-access-signature
     * https://docs.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1
     */
    public static final String storageConnectionString = "DefaultEndpointsProtocol=https;"
            + "AccountName=[MY_ACCOUNT_NAME];"
            + "AccountKey=[MY_ACCOUNT_KEY]";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment()).commit();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Runs the blob getting started sample.
     */
    public void runBlobGettingStartedSample(View view) {
        new BlobGettingStartedTask(this, (TextView) findViewById(R.id.textView))
                .execute();
    }

    /**
     * Runs the queue getting started sample.
     */
    public void runQueueGettingStartedSample(View view) {
        new QueueGettingStartedTask(this,
                (TextView) findViewById(R.id.textView)).execute();
    }

    /**
     * Runs the table getting started sample.
     */
    public void runTableGettingStartedSample(View view) {
        new TableGettingStartedTask(this,
                (TextView) findViewById(R.id.textView)).execute();
    }

    /**
     * Runs the table payload format sample.
     */
    public void runTablePayloadFormatSample(View view) {
        new TablePayloadFormatTask(this, (TextView) findViewById(R.id.textView))
                .execute();
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container,
                    false);
            return rootView;
        }
    }

    /**
     * Prints the specified text value to the view and to LogCat.
     *
     * @param view
     *            The view to print to.
     * @param value
     *            The value to print.
     */
    public void outputText(final TextView view, final String value) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.append(value + "\n");
                System.out.println(view);
            }
        });
    }

    /**
     * Clears the text from the specified view.
     *
     * @param view
     *            The view to clear.
     */
    public void clearText(final TextView view) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.setText("");
            }
        });
    }

    /**
     * Prints out the exception information .
     */
    public void printException(Throwable t) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        t.printStackTrace(printWriter);
        outputText(
                (TextView) findViewById(R.id.textView),
                String.format(
                        "Got an exception from running samples. Exception details:\n%s\n",
                        stringWriter.toString()));
    }

    /**
     * Prints out the sample start information .
     */
    public void printSampleStartInfo(String sampleName) {
        TextView view = (TextView) findViewById(R.id.textView);
        clearText(view);
        outputText(view, String.format(
                "The Azure storage client library sample %s is starting...",
                sampleName));
    }

    /**
     * Prints out the sample complete information .
     */
    public void printSampleCompleteInfo(String sampleName) {
        outputText((TextView) findViewById(R.id.textView), String.format(
                "The Azure storage client library sample %s completed.\n",
                sampleName));
    }

}


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

import java.net.HttpURLConnection;
import java.security.InvalidParameterException;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.StorageException;

/**
 * RESERVED FOR INTERNAL USE. Provides an implementation of the Canonicalizer class for requests against Table
 * Service under the Shared Key authentication scheme.
 */
final class TableCanonicalizer extends Canonicalizer {

    /**
     * Constructs a canonicalized string for signing a request.
     *
     * @param conn
     *            the HttpURLConnection to canonicalize
     * @param accountName
     *            the account name associated with the request
     * @param contentLength
     *            the length of the content written to the outputstream in bytes, -1 if unknown
     * @return a canonicalized string.
     * @throws StorageException
     */
    @Override
    protected String canonicalize(final HttpURLConnection conn, final String accountName, final Long contentLength)
            throws StorageException {

        if (contentLength < -1) {
            throw new InvalidParameterException(SR.INVALID_CONTENT_LENGTH);
        }

        return canonicalizeTableHttpRequest(conn.getURL(), accountName, conn.getRequestMethod(),
                Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_TYPE), contentLength, null,
                conn);
    }
}