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

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;

import com.fasterxml.jackson.core.JsonParseException;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageExtendedErrorInformation;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.RequestLocationMode;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.Utility;

/**
 * A class that extends {@link TableOperation} to implement a query to retrieve a single table entity. To execute a
 * {@link QueryTableOperation} instance, call the execute method on a {@link CloudTableClient} instance.
 * This operation can be executed directly or as part of a {@link TableBatchOperation}. If the
 * {@link QueryTableOperation} returns an entity result, it is stored in the corresponding {@link TableResult} returned
 * by the execute method.
 */
public class QueryTableOperation extends TableOperation {
    private EntityResolver<?> resolver;

    private Class<? extends TableEntity> clazzType;

    private String partitionKey;

    private String rowKey;

    private boolean isPrimaryOnlyRetrieve = false;

    /**
     * Default constructor.
     */
    protected QueryTableOperation() {
        super(null, TableOperationType.RETRIEVE);
    }

    /**
     * Constructs a {@link QueryTableOperation} instance to retrieve a single table entity with the specified partition
     * key and row key.
     *
     * @param partitionKey
     *            A String containing the PartitionKey value for the entity.
     * @param rowKey
     *            A String containing the RowKey value for the entity.
     */
    QueryTableOperation(final String partitionKey, final String rowKey) {
        super(null, TableOperationType.RETRIEVE);
        Utility.assertNotNull("partitionKey", partitionKey);
        this.partitionKey = partitionKey;
        this.rowKey = rowKey;
    }

    /**
     * Gets the PartitionKey value for the entity to retrieve.
     *
     * @return
     *         A String containing the PartitionKey value for the entity.
     */
    public String getPartitionKey() {
        return this.partitionKey;
    }

    /**
     * Gets the resolver to project the entity retrieved as a particular type.
     *
     * @return
     *         The {@link EntityResolver} instance.
     */
    public EntityResolver<?> getResolver() {
        return this.resolver;
    }

    /**
     * Gets the RowKey value for the entity to retrieve.
     *
     * @return
     *         A String containing the RowKey value for the entity.
     */
    public String getRowKey() {
        return this.rowKey;
    }

    /**
     * Reserved for internal use. Gets the class type of the entity returned by the query.
     *
     * @return
     *         The java.lang.Class implementing {@link TableEntity} that represents the entity type for the
     *         query.
     */
    protected Class<? extends TableEntity> getClazzType() {
        return this.clazzType;
    }

    /**
     * Reserved for internal use. Parses the table operation response into a {@link TableResult} to return.
     *
     * @param inStream
     *            An InputStream containing the response to a query operation.
     * @param httpStatusCode
     *            The HTTP status code returned from the operation request.
     * @param etagFromHeader
     *            The String containing the Etag returned with the operation response.
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation.
     * @return
     *         The {@link TableResult} representing the result of the query operation.
     *
     * @throws InstantiationException
     *             if an error occurs in object construction.
     * @throws IllegalAccessException
     *             if an error occurs in reflection on an object type.
     * @throws StorageException
     *             if an error occurs in the storage operation.
     * @throws IOException
     *             if an error occurs while accessing the {@link InputStream} with Json.
     * @throws JsonParseException
     *             if an error occurs while parsing the Json, if Json is used.
     */
    @Override
    protected TableResult parseResponse(final InputStream inStream, final int httpStatusCode, String etagFromHeader,
            final OperationContext opContext, final TableRequestOptions options) throws
            InstantiationException, IllegalAccessException, StorageException, JsonParseException, IOException {
        TableResult res = TableDeserializer.parseSingleOpResponse(inStream, options, httpStatusCode,
                this.getClazzType(), this.getResolver(), opContext);
        res.setEtag(etagFromHeader);
        return res;
    }

    /**
     * Reserved for internal use. Performs a retrieve operation on the specified table, using the specified
     * {@link TableRequestOptions} and {@link OperationContext}.
     *

     * This method will invoke the Storage Service REST API to execute this table operation, using the Table service
     * endpoint and storage account credentials in the {@link CloudTableClient} object.
     *
     * @param client
     *            A {@link CloudTableClient} instance specifying the Table service endpoint and storage account
     *            credentials to use.
     * @param tableName
     *            A String containing the name of the table to query.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation.
     *
     * @return
     *         A {@link TableResult} containing the results of executing the query operation.
     *
     * @throws StorageException
     *             if an error occurs in the storage operation.
     */
    protected TableResult performRetrieve(final CloudTableClient client, final String tableName,
            final TableRequestOptions options, final OperationContext opContext) throws StorageException {

        return ExecutionEngine.executeWithRetry(client, this, this.retrieveImpl(client, tableName, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudTableClient, QueryTableOperation, TableResult> retrieveImpl(
            final CloudTableClient client, final String tableName, final TableRequestOptions options) {
        final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName);
        if (this.getClazzType() != null) {
            Utility.checkNullaryCtor(this.getClazzType());
        }
        else {
            Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, this.getResolver());
        }

        final StorageRequest<CloudTableClient, QueryTableOperation, TableResult> getRequest = new StorageRequest<CloudTableClient, QueryTableOperation, TableResult>(
                options, client.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudTableClient client, QueryTableOperation operation,
                    OperationContext context) throws Exception {
                return TableRequest
                        .query(client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()), options,
                                null/* Query Builder */, context, tableName,
                                generateRequestIdentity(isTableEntry, operation.getPartitionKey()), null/* Continuation Token */);
            }

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

            @Override
            public TableResult preProcessResponse(QueryTableOperation operation, CloudTableClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK
                        && this.getResult().getStatusCode() != HttpURLConnection.HTTP_NOT_FOUND) {
                    this.setNonExceptionedRetryableFailure(true);
                }
                return null;
            }

            @Override
            public TableResult postProcessResponse(HttpURLConnection connection, QueryTableOperation operation,
                    CloudTableClient client, OperationContext context, TableResult storageObject) throws Exception {
                if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
                    // Empty result
                    return new TableResult(this.getResult().getStatusCode());
                }

                // Parse response for updates
                InputStream inStream = connection.getInputStream();
                TableResult res = parseResponse(inStream, this.getResult().getStatusCode(), this.getConnection()
                        .getHeaderField(TableConstants.HeaderConstants.ETAG), context, options);
                connection.getInputStream().close();

                return res;
            }

            @Override
            public StorageExtendedErrorInformation parseErrorDetails() {
                return TableStorageErrorDeserializer.parseErrorDetails(this);
            }
        };

        return getRequest;
    }

    /**
     * Reserved for internal use. Sets the class type of the entity returned by the query.
     *
     * @param clazzType
     *            The java.lang.Class implementing {@link TableEntity} that represents the entity type for
     *            the query.
     */
    protected void setClazzType(final Class<? extends TableEntity> clazzType) {
        Utility.assertNotNull("clazzType", clazzType);
        Utility.checkNullaryCtor(clazzType);
        this.clazzType = clazzType;
    }

    /**
     * Reserved for internal use. Sets the PartitionKey value for the entity to retrieve.
     *
     * @param partitionKey
     *            A String containing the PartitionKey value for the entity.
     */
    protected void setPartitionKey(final String partitionKey) {
        this.partitionKey = partitionKey;
    }

    /**
     * Reserved for internal use. Sets the resolver to project the entity retrieved as a particular type.
     *
     * @param resolver
     *            The {@link EntityResolver} instance to use.
     */
    protected void setResolver(final EntityResolver<?> resolver) {
        Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, resolver);
        this.resolver = resolver;
    }

    /**
     * Reserved for internal use. Sets the RowKey value for the entity to retrieve.
     *
     * @param rowKey
     *            A String containing the RowKey value for the entity.
     */
    protected void setRowKey(final String rowKey) {
        this.rowKey = rowKey;
    }

    /**
     * @return the isPrimaryOnlyRetrieve
     */
    protected final boolean isPrimaryOnlyRetrieve() {
        return this.isPrimaryOnlyRetrieve;
    }

    /**
     * @param isPrimaryOnlyRetrieve
     *            the isPrimaryOnlyRetrieve to set
     */
    protected void setPrimaryOnlyRetrieve(boolean isPrimaryOnlyRetrieve) {
        this.isPrimaryOnlyRetrieve = isPrimaryOnlyRetrieve;
    }
}


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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;

/**
 * Reserved for internal use. A class used to read and write MIME requests and responses.
 */
final class MimeHelper {
    /**
     * Reserved for internal use. Reads the response stream from a batch operation into an ArrayList of
     * {@link MimePart} objects.
     *
     * @param inStream
     *            An {@link InputStream} containing the operation response stream.
     * @param expectedBundaryName
     *            A String containing the MIME part boundary string.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param format
     *            The {@link TablePayloadFormat} that will be used for parsing
     * @return
     *         An ArrayList of {@link MimePart} objects parsed from the input stream.
     * @throws StorageException
     *             if a storage service error occurs.
     * @throws IOException
     *             if an error occurs while accessing the stream with Json.
     */
    static ArrayList<MimePart> readBatchResponseStream(final InputStream inStream, final String expectedBundaryName,
            final OperationContext opContext, TablePayloadFormat format) throws IOException, StorageException {
        final ArrayList<MimePart> result = new ArrayList<MimePart>();
        final InputStreamReader streamReader = new InputStreamReader(inStream, Constants.UTF8_CHARSET);
        final BufferedReader reader = new BufferedReader(streamReader);
        final String mungedExpectedBoundaryName = "--".concat(expectedBundaryName);

        final MimeHeader docHeader = readMimeHeader(reader, opContext);
        if (docHeader.boundary == null || !docHeader.boundary.equals(mungedExpectedBoundaryName)) {
            throw generateMimeParseException();
        }

        MimeHeader currHeader = null;

        // No explicit changeset present
        if (docHeader.subBoundary == null) {
            do {
                result.add(readMimePart(reader, docHeader.boundary, opContext));
                currHeader = readMimeHeader(reader, opContext);
            } while (currHeader != null);
        }
        else {
            // explicit changeset present.
            currHeader = readMimeHeader(reader, opContext);
            if (currHeader == null) {
                throw generateMimeParseException();
            }
            else {
                do {
                    result.add(readMimePart(reader, docHeader.subBoundary, opContext));
                    currHeader = readMimeHeader(reader, opContext);
                } while (currHeader != null);
            }
        }

        return result;
    }

    /**
     * Reserved for internal use. Writes the batch operation to the output stream using batch request syntax.
     * Batch request syntax is described in the MSDN topic Performing Entity Group
     * Transactions.
     *
     * @param outStream
     *            The {@link OutputStream} to write the batch request to.
     * @param tableName
     *            A String containing the name of the table to apply each operation to.
     * @param batch
     *            A {@link TableBatchOperation} containing the operations to write to the output stream
     * @param batchID
     *            A String containing the identifier to use as the MIME boundary for the batch request.
     * @param changeSet
     *            A String containing the identifier to use as the MIME boundary for operations within the
     *            batch.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @throws IOException
     *             if an IO error occurs.
     * @throws URISyntaxException
     *             if an invalid URI is used.
     * @throws StorageException
     *             if an error occurs accessing the Storage service.
     */
    static void writeBatchToStream(final OutputStream outStream, final TableRequestOptions options,
            final String tableName, final URI baseUri, final TableBatchOperation batch, final String batchID,
            final String changeSet, final OperationContext opContext) throws IOException, URISyntaxException,
            StorageException {
        final OutputStreamWriter outWriter = new OutputStreamWriter(outStream, Constants.UTF8_CHARSET);

        MimePart mimePart;
        int contentID = 0;
        boolean isQuery = batch.size() == 1 && batch.get(0).getOperationType() == TableOperationType.RETRIEVE;

        // when batch is made, a check is done to make sure only one retrieve is added
        if (isQuery) {
            final QueryTableOperation qOp = (QueryTableOperation) batch.get(0);
            // Write MIME batch Header
            MimeHelper.writeMIMEBoundary(outWriter, batchID);

            mimePart = new MimePart();
            mimePart.op = qOp.getOperationType();

            UriQueryBuilder builder = new UriQueryBuilder();
            mimePart.requestIdentity = builder.addToURI(PathUtility.appendPathToSingleUri(baseUri,
                    qOp.generateRequestIdentityWithTable(tableName)));

            mimePart.headers.put(Constants.HeaderConstants.ACCEPT,
                    generateAcceptHeaderValue(options.getTablePayloadFormat()));
            mimePart.headers.put(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION,
                    TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE);

            outWriter.write(mimePart.toRequestString());
        }
        else {
            // Write MIME batch Header
            MimeHelper.writeMIMEBoundary(outWriter, batchID);
            MimeHelper.writeMIMEContentType(outWriter, changeSet);
            outWriter.write("\r\n");

            // Write each operation
            for (final TableOperation op : batch) {
                // New mime part for changeset
                MimeHelper.writeMIMEBoundary(outWriter, changeSet);

                mimePart = new MimePart();
                mimePart.op = op.getOperationType();

                UriQueryBuilder builder = new UriQueryBuilder();
                mimePart.requestIdentity = builder.addToURI(PathUtility.appendPathToSingleUri(baseUri,
                        op.generateRequestIdentityWithTable(tableName)));

                mimePart.headers.put(TableConstants.HeaderConstants.CONTENT_ID, Integer.toString(contentID));
                mimePart.headers.put(Constants.HeaderConstants.ACCEPT,
                        generateAcceptHeaderValue(options.getTablePayloadFormat()));
                mimePart.headers.put(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION,
                        TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE);

                if (op.getOperationType() == TableOperationType.INSERT_OR_MERGE
                        || op.getOperationType() == TableOperationType.MERGE) {
                    // post tunnelling
                    mimePart.headers.put(TableConstants.HeaderConstants.X_HTTP_METHOD,
                            TableOperationType.MERGE.toString());
                }

                // etag
                if (op.getOperationType() == TableOperationType.DELETE
                        || op.getOperationType() == TableOperationType.REPLACE
                        || op.getOperationType() == TableOperationType.MERGE) {
                    if (op.getEntity() != null && op.getEntity().getEtag() != null) {
                        mimePart.headers.put(Constants.HeaderConstants.IF_MATCH, op.getEntity().getEtag());
                    }
                }

                // prefer header
                if (op.getOperationType() == TableOperationType.INSERT) {
                    mimePart.headers.put(TableConstants.HeaderConstants.PREFER,
                            op.getEchoContent() ? TableConstants.HeaderConstants.RETURN_CONTENT
                                    : TableConstants.HeaderConstants.RETURN_NO_CONTENT);
                }

                if (op.getOperationType() != TableOperationType.DELETE) {
                    mimePart.headers.put(Constants.HeaderConstants.CONTENT_TYPE,
                            TableConstants.HeaderConstants.JSON_CONTENT_TYPE);
                    mimePart.payload = writeStringForOperation(op, options.getTablePayloadFormat(), opContext);
                    mimePart.headers.put(Constants.HeaderConstants.CONTENT_LENGTH,
                            Integer.toString(mimePart.payload.getBytes(Constants.UTF8_CHARSET).length));
                }

                // write the request (no body)
                outWriter.write(mimePart.toRequestString());

                contentID = contentID + 1;
            }
        }

        if (!isQuery) {
            // end changeset
            MimeHelper.writeMIMEBoundaryClosure(outWriter, changeSet);
        }
        // end batch
        MimeHelper.writeMIMEBoundaryClosure(outWriter, batchID);

        outWriter.flush();
    }

    /**
     * Reserved for internal use. A static factory method that constructs a {@link MimeHeader} by parsing the MIME
     * header
     * data from a {@link BufferedReader}.
     *
     * @param reader
     *            The {@link BufferedReader} containing the response stream to parse.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @return
     *         A {@link MimeHeader} constructed by parsing the MIME header data from the {@link BufferedReader}.
     * @throws IOException
     *             if an error occurs accessing the input stream.
     * @throws StorageException
     *             if an error occurs parsing the input stream.
     */
    private static MimeHeader readMimeHeader(final BufferedReader reader, final OperationContext opContext)
            throws IOException, StorageException {
        final MimeHeader retHeader = new MimeHeader();
        reader.mark(1024 * 1024);

        // First thing is separator
        retHeader.boundary = getNextLineSkippingBlankLines(reader);
        if (retHeader.boundary.endsWith("--")) {
            return null;
        }
        if (!retHeader.boundary.startsWith("--")) {
            reader.reset();
            return null;
        }

        for (int m = 0; m < 2; m++) {
            final String tempString = reader.readLine();
            if (tempString == null || tempString.length() == 0) {
                break;
            }

            if (tempString.startsWith("Content-Type:")) {
                final String[] headerVals = tempString.split("Content-Type: ");
                if (headerVals == null || headerVals.length != 2) {
                    throw generateMimeParseException();
                }
                retHeader.contentType = headerVals[1];
            }
            else if (tempString.startsWith("Content-Transfer-Encoding:")) {
                final String[] headerVals = tempString.split("Content-Transfer-Encoding: ");
                if (headerVals == null || headerVals.length != 2) {
                    throw generateMimeParseException();
                }
                retHeader.contentTransferEncoding = headerVals[1];
            }
            else {
                throw generateMimeParseException();
            }
        }

        // Validate headers
        if (Utility.isNullOrEmpty(retHeader.boundary) || retHeader.contentType == null) {
            throw generateMimeParseException();
        }

        if (retHeader.contentType.startsWith("multipart/mixed; boundary=")) {
            final String[] headerVals = retHeader.contentType.split("multipart/mixed; boundary=");
            if (headerVals == null || headerVals.length != 2) {
                throw generateMimeParseException();
            }
            retHeader.subBoundary = "--".concat(headerVals[1]);
        }
        else if (!retHeader.contentType.equals("application/http")) {
            throw generateMimeParseException();
        }

        if (retHeader.contentTransferEncoding != null && !retHeader.contentTransferEncoding.equals("binary")) {
            throw generateMimeParseException();
        }

        return retHeader;
    }

    // Returns at start of next mime boundary header
    /**
     * Reserved for internal use. A static factory method that generates a {@link MimePart} containing the next MIME
     * part read from the {@link BufferedReader}.
     * The {@link BufferedReader} is left positioned at the start of the next MIME boundary header.
     *
     * @param reader
     *            The {@link BufferedReader} containing the response stream to parse.
     * @param boundary
     *            A String containing the MIME part boundary string.
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @return
     *         A {@link MimePart} constructed by parsing the next MIME part data from the {@link BufferedReader}.
     * @throws IOException
     *             if an error occured accessing the input stream.
     * @throws StorageException
     *             if an error occured parsing the input stream.
     */
    private static MimePart readMimePart(final BufferedReader reader, final String boundary,
            final OperationContext opContext) throws IOException, StorageException {
        final MimePart retPart = new MimePart();
        // Read HttpStatus code
        String tempStr = getNextLineSkippingBlankLines(reader);
        if (!tempStr.startsWith("HTTP/1.1 ")) {
            throw generateMimeParseException();
        }

        final String[] headerVals = tempStr.split(" ");

        if (headerVals.length < 3) {
            throw generateMimeParseException();
        }

        retPart.httpStatusCode = Integer.parseInt(headerVals[1]);
        // "HTTP/1.1 XXX ".length() => 13
        retPart.httpStatusMessage = tempStr.substring(13);

        // Read headers
        tempStr = reader.readLine();
        while (tempStr != null && tempStr.length() > 0) {
            final String[] headerParts = tempStr.split(": ");
            if (headerParts.length < 2) {
                throw generateMimeParseException();
            }

            retPart.headers.put(headerParts[0], headerParts[1]);
            tempStr = reader.readLine();
        }

        // Store json payload
        reader.mark(1024 * 1024);
        tempStr = getNextLineSkippingBlankLines(reader);

        if (tempStr == null) {
            throw generateMimeParseException();
        }

        // empty body
        if (tempStr.startsWith(boundary)) {
            reader.reset();
            retPart.payload = Constants.EMPTY_STRING;
            return retPart;
        }
        final StringBuilder payloadBuilder = new StringBuilder();
        // read until mime closure or end of file
        while (!tempStr.startsWith(boundary)) {
            payloadBuilder.append(tempStr);
            reader.mark(1024 * 1024);
            tempStr = getNextLineSkippingBlankLines(reader);
            if (tempStr == null) {
                throw generateMimeParseException();
            }
        }

        // positions stream at start of next MIME Header
        reader.reset();

        retPart.payload = payloadBuilder.toString();

        return retPart;
    }

    /**
     * Reserved for internal use. Writes a MIME part boundary to the output stream.
     *
     * @param outWriter
     *            The {@link OutputStreamWriter} to write the MIME part boundary to.
     * @param boundaryID
     *            The String containing the MIME part boundary string.
     * @throws IOException
     *             if an error occurs writing to the output stream.
     */
    private static void writeMIMEBoundary(final OutputStreamWriter outWriter, final String boundaryID)
            throws IOException {
        outWriter.write(String.format("--%s\r\n", boundaryID));
    }

    /**
     * Reserved for internal use. Writes a MIME part boundary closure to the output stream.
     *
     * @param outWriter
     *            The {@link OutputStreamWriter} to write the MIME part boundary closure to.
     * @param boundaryID
     *            The String containing the MIME part boundary string.
     * @throws IOException
     *             if an error occurs writing to the output stream.
     */
    private static void writeMIMEBoundaryClosure(final OutputStreamWriter outWriter, final String boundaryID)
            throws IOException {
        outWriter.write(String.format("--%s--\r\n", boundaryID));
    }

    /**
     * Reserved for internal use. Writes a MIME content type string to the output stream.
     *
     * @param outWriter
     *            The {@link OutputStreamWriter} to write the MIME content type string to.
     * @param boundaryID
     *            The String containing the MIME part boundary string.
     * @throws IOException
     *             if an error occurs writing to the output stream.
     */
    private static void writeMIMEContentType(final OutputStreamWriter outWriter, final String boundaryName)
            throws IOException {
        outWriter.write(String.format("Content-Type: multipart/mixed; boundary=%s\r\n", boundaryName));
    }

    /**
     * Reserved for internal use. Generates a String containing the entity associated with an operation in
     * Json format.
     *
     * @param operation
     *            A {@link TableOperation} containing the entity to write to the returned String.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @return
     *         A String containing the entity associated with the operation in Json format
     * @throws StorageException
     *             if a Storage error occurs.
     * @throws IOException
     */
    private static String writeStringForOperation(final TableOperation operation, TablePayloadFormat format,
            final OperationContext opContext) throws StorageException, IOException {
        Utility.assertNotNull("entity", operation.getEntity());
        final StringWriter outWriter = new StringWriter();

        TableEntitySerializer.writeSingleEntity(outWriter, operation.getEntity(), false, opContext);
        outWriter.write("\r\n");

        return outWriter.toString();
    }

    private static String generateAcceptHeaderValue(TablePayloadFormat payloadFormat) {
        if (payloadFormat == TablePayloadFormat.JsonFullMetadata) {
            return TableConstants.HeaderConstants.JSON_FULL_METADATA_ACCEPT_TYPE;
        }
        else if (payloadFormat == TablePayloadFormat.Json) {
            return TableConstants.HeaderConstants.JSON_ACCEPT_TYPE;
        }
        else {
            return TableConstants.HeaderConstants.JSON_NO_METADATA_ACCEPT_TYPE;
        }
    }

    /**
     * Reserved for internal use. A static factory method that generates a {@link StorageException} for invalid MIME
     * responses.
     *
     * @return
     *         The {@link StorageException} for the invalid MIME response.
     */
    private static StorageException generateMimeParseException() {
        return new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, SR.INVALID_MIME_RESPONSE,
                Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
    }

    /**
     * Reserved for internal use. Returns the next non-blank line from the {@link BufferedReader}.
     *
     * @param reader
     *            The {@link BufferedReader} to read lines from.
     * @return
     *         A String containing the next non-blank line from the {@link BufferedReader}, or
     *         null.
     * @throws IOException
     *             if an error occurs reading from the {@link BufferedReader}.
     */
    private static String getNextLineSkippingBlankLines(final BufferedReader reader) throws IOException {
        String tString = null;
        do {
            tString = reader.readLine();
        } while (tString != null && tString.length() == 0);

        return tString;
    }
}


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

import java.util.HashMap;

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

/**
 * A {@link TableEntity} type which allows callers direct access to the property map of the entity. This class extends
 * {@link TableServiceEntity} to eliminate the use of reflection for serialization and deserialization.
 *
 */
public class DynamicTableEntity extends TableServiceEntity {
    private HashMap<String, EntityProperty> properties = new HashMap<String, EntityProperty>();

    /**
     * Nullary default constructor.
     */
    public DynamicTableEntity() {
        // Empty constructor.
    }

    /**
     * Initializes a new instance of the {@link DynamicTableEntity} class with the specified partition key and row key.
     *
     * @param partitionKey
     *            A String which represents the partition key of the {@link DynamicTableEntity} to be initialized.
     * @param rowKey
     *            A String which represents the row key of the {@link DynamicTableEntity} to be initialized.
     */
    public DynamicTableEntity(String partitionKey, String rowKey) {
        super(partitionKey, rowKey);
    }

    /**
     * Constructs a {@link DynamicTableEntity} instance using the specified property map.
     *
     * @param properties
     *            A java.util.HashMap containing a map of String property names to
     *            {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}.
     */
    public DynamicTableEntity(final HashMap<String, EntityProperty> properties) {
        this.setProperties(properties);
    }

    /**
     * Initializes a new instance of the {@link DynamicTableEntity} class with the specified partition key and row key.
     *
     * @param partitionKey
     *            A String which represents the partition key of the {@link DynamicTableEntity} to be initialized.
     * @param rowKey
     *            A String which represents the row key of the {@link DynamicTableEntity} to be initialized.
     * @param properties
     *            A java.util.HashMap containing a map of String property names to
     *            {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}.
     */
    public DynamicTableEntity(String partitionKey, String rowKey, final HashMap<String, EntityProperty> properties) {
        this(partitionKey, rowKey, null /* etag */, properties);
    }

    /**
     * Initializes a new instance of the {@link DynamicTableEntity} class with the specified partition key and row key.
     *
     * @param partitionKey
     *            A String which represents the partition key of the {@link DynamicTableEntity} to be initialized.
     * @param rowKey
     *            A String which represents the row key of the {@link DynamicTableEntity} to be initialized.
     * @param etag
     *            The ETag of the {@link DynamicTableEntity} to be initialized. This value is used to determine if the table
     *            entity has changed since it was last read from Microsoft Azure storage. The client cannot update this value
     *            on the service.
     * @param properties
     *            A java.util.HashMap containing a map of String property names to
     *            {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}.
     */
    public DynamicTableEntity(String partitionKey, String rowKey, String etag,
            final HashMap<String, EntityProperty> properties) {
        super(partitionKey, rowKey);

        this.etag = etag;
        this.setProperties(properties);
    }

    /**
     * Gets the property map for this {@link DynamicTableEntity} instance.
     *
     * @return
     *         A java.util.HashMap containing the map of String property names to
     *         {@link EntityProperty} data typed values for this {@link DynamicTableEntity} instance.
     */
    public HashMap<String, EntityProperty> getProperties() {
        return this.properties;
    }

    /**
     * Populates this {@link DynamicTableEntity} instance using the specified map of property names to
     * {@link EntityProperty} data typed values.
     *
     * @param properties
     *            The java.util.HashMap of String property names to {@link EntityProperty}
     *            data typed values to store in this {@link DynamicTableEntity} instance.
     * @param opContext
     *            An {@link OperationContext} object used to track the execution of the operation.
     */
    @Override
    public void readEntity(final HashMap<String, EntityProperty> properties, final OperationContext opContext) {
        this.setProperties(properties);
    }

    /**
     * Sets the property map for this {@link DynamicTableEntity} instance.
     *
     * @param properties
     *            A java.util.HashMap containing the map of String property names to
     *            {@link EntityProperty} data typed values to set in this {@link DynamicTableEntity} instance.
     */
    public void setProperties(final HashMap<String, EntityProperty> properties) {
        this.properties = properties;
    }

    /**
     * Returns the map of property names to {@link EntityProperty} data values from this {@link DynamicTableEntity}
     * instance.
     *
     * @param opContext
     *            An {@link OperationContext} object used to track the execution of the operation.
     *
     * @return
     *         A java.util.HashMap containing the map of String property names to
     *         {@link EntityProperty} data typed values stored in this {@link DynamicTableEntity} instance.
     *
     * @throws StorageException
     *             if a Storage service error occurs.
     */
    @Override
    public HashMap<String, EntityProperty> writeEntity(final OperationContext opContext) throws StorageException {
        return this.getProperties();
    }
}


/**
 * 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.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

import com.microsoft.azure.storage.StorageException;

/**
 * RESERVED FOR INTERNAL USE. A helper class to help modify the query string of a URI
 */
public final class UriQueryBuilder {
    /**
     * Stores the one to one key/ value collection of query parameters.
     */
    private final HashMap<String, ArrayList<String>> parameters = new HashMap<String, ArrayList<String>>();

    /**
     * Adds a value to the URI with escaping.
     *
     * @param name
     *            the query key name.
     * @param value
     *            the query value.
     * @throws StorageException
     */
    public void add(final String name, final String value) throws StorageException {
        if (Utility.isNullOrEmpty(name)) {
            throw new IllegalArgumentException(SR.QUERY_PARAMETER_NULL_OR_EMPTY);
        }

        this.insertKeyValue(name, value);
    }

    /**
     * Add query parameter to an existing Uri. This takes care of any existing query parameters in the Uri.
     *
     * @param uri
     *            the original uri.
     * @return the appended uri
     * @throws URISyntaxException
     *             if the resulting uri is invalid.
     * @throws StorageException
     */
    public URI addToURI(final URI uri) throws URISyntaxException, StorageException {
        final String origRawQuery = uri.getRawQuery();
        final String rawFragment = uri.getRawFragment();
        final String uriString = uri.resolve(uri).toASCIIString();

        final HashMap<String, String[]> origQueryMap = PathUtility.parseQueryString(origRawQuery);

        // Try/Insert original queries to map

        for (final Entry<String, String[]> entry : origQueryMap.entrySet()) {
            for (final String val : entry.getValue()) {
                this.insertKeyValue(entry.getKey(), val);
            }
        }

        final StringBuilder retBuilder = new StringBuilder();

        // has a fragment
        if (Utility.isNullOrEmpty(origRawQuery) && !Utility.isNullOrEmpty(rawFragment)) {
            final int bangDex = uriString.indexOf('#');
            retBuilder.append(uriString.substring(0, bangDex));
        }
        else if (!Utility.isNullOrEmpty(origRawQuery)) {
            // has a query
            final int queryDex = uriString.indexOf('?');
            retBuilder.append(uriString.substring(0, queryDex));
        }
        else {
            // no fragment or query
            retBuilder.append(uriString);
            if (uri.getRawPath().length() <= 0) {
                retBuilder.append("/");
            }
        }

        final String finalQuery = this.toString();

        if (finalQuery.length() > 0) {
            retBuilder.append("?");
            retBuilder.append(finalQuery);
        }

        if (!Utility.isNullOrEmpty(rawFragment)) {
            retBuilder.append("#");
            retBuilder.append(rawFragment);
        }

        return new URI(retBuilder.toString());
    }

    /**
     * Inserts a key / value in the Hashmap, assumes the value is already utf-8 encoded.
     *
     * @param key
     *            the key to insert
     * @param value
     *            the value to insert
     * @throws StorageException
     */
    private void insertKeyValue(String key, String value) throws StorageException {
        if (value != null) {
            value = Utility.safeEncode(value);
        }
        if (!key.startsWith("$")) {
            key = Utility.safeEncode(key);
        }

        ArrayList<String> list = this.parameters.get(key);
        if (list == null) {
            list = new ArrayList<String>();
            list.add(value);
            this.parameters.put(key, list);
        }
        else {
            if (!list.contains(value)) {
                list.add(value);
            }
        }
    }

    /**
     * Returns a string that represents this instance. This will construct the full URI.
     *
     * @return a string that represents this instance.
     */
    @Override
    public String toString() {
        final StringBuilder outString = new StringBuilder();
        Boolean isFirstPair = true;

        for (final String key : this.parameters.keySet()) {
            if (this.parameters.get(key) != null) {
                for (final String val : this.parameters.get(key)) {
                    if (isFirstPair) {
                        isFirstPair = false;
                    }
                    else {
                        outString.append("&");
                    }

                    outString.append(String.format("%s=%s", key, val));
                }
            }
        }

        return outString.toString();
    }
}


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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.UUID;

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

/**
 * Reserved for internal use. A class used internally during the reflection process to determine which properties should
 * be serialized.
 */
final class PropertyPair {

    /**
     * Reserved for internal use. A static factory method to generate a map of property names to {@link PropertyPair}
     * instances for the specified class type. Checks if the cache is enabled and if so tries to get the property pairs
     * from it. Otherwise, uses reflection to find pairs of getter and setter methods that are annotated with
     * {@link StoreAs} with a common property name, or of the form getPropertyName and
     * setPropertyName, with a common type for the getter return value and the setter parameter,
     * and stores the methods and the property name for each pair found in a map for use in serializing and
     * deserializing entity data. If the cache is enabled and the mapping was not present, adds it to the cache.
     *
     * @param clazzType
     *            The class type to check for matching getter and setter methods with a common return and parameter
     *            type, respectively.
     */
    protected static HashMap<String, PropertyPair> generatePropertyPairs(final Class<?> clazzType) {
        if (!TableServiceEntity.isReflectedEntityCacheDisabled()) {
            HashMap<String, PropertyPair> props = new HashMap<String, PropertyPair>();
            props = TableServiceEntity.getReflectedEntityCache().get(clazzType);

            if (props == null) {
                props = PropertyPair.generatePropertyPairsHelper(clazzType);
                TableServiceEntity.getReflectedEntityCache().put(clazzType, props);
            }

            return props;
        }
        else {
            return PropertyPair.generatePropertyPairsHelper(clazzType);
        }
    }

    /**
     * Reserved for internal use. A static factory method to generate a map of property names to {@link PropertyPair}
     * instances for the specified class type. Uses reflection to find pairs of getter and setter methods that are
     * annotated with {@link StoreAs} with a common property name, or of the form getPropertyName
     * and setPropertyName, with a common type for the getter return value and the
     * setter parameter, and stores the methods and the property name for each pair found in a map for use in
     * serializing and deserializing entity data.
     *
     * @param clazzType
     *            The class type to check for matching getter and setter methods with a common return and parameter
     *            type, respectively.
     */
    private static HashMap<String, PropertyPair> generatePropertyPairsHelper(final Class<?> clazzType) {
        final Method[] methods = clazzType.getMethods();
        final HashMap<String, PropertyPair> propMap = new HashMap<String, PropertyPair>();

        String propName = null;
        PropertyPair currProperty = null;

        for (final Method m : methods) {
            if (m.getName().length() < 4 || (!m.getName().startsWith("get") && !m.getName().startsWith("set"))) {
                continue;
            }

            propName = m.getName().substring(3);

            // Skip interface methods, these will be called explicitly
            if (propName.equals(TableConstants.PARTITION_KEY) || propName.equals(TableConstants.ROW_KEY)
                    || propName.equals(TableConstants.TIMESTAMP) || propName.equals("Etag")
                    || propName.equals("LastModified")) {
                continue;
            }

            if (propMap.containsKey(propName)) {
                currProperty = propMap.get(propName);
            }
            else {
                currProperty = new PropertyPair();
                currProperty.name = propName;
                propMap.put(propName, currProperty);
            }

            if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) {
                currProperty.type = m.getReturnType();
                currProperty.getter = m;
            }
            else if (m.getName().startsWith("set") && m.getParameterTypes().length == 1
                    && void.class.equals(m.getReturnType())) {
                currProperty.setter = m;
            }

            // Check for StoreAs Annotation
            final StoreAs storeAsInstance = m.getAnnotation(StoreAs.class);
            if (storeAsInstance != null) {
                if (Utility.isNullOrEmpty(storeAsInstance.name())) {
                    throw new IllegalArgumentException(String.format(SR.STOREAS_USED_ON_EMPTY_PROPERTY,
                            currProperty.name));
                }

                if (currProperty.effectiveName != null && !currProperty.effectiveName.equals(currProperty.name)
                        && !currProperty.effectiveName.equals(storeAsInstance.name())) {
                    throw new IllegalArgumentException(String.format(SR.STOREAS_DIFFERENT_FOR_GETTER_AND_SETTER,
                            currProperty.name));
                }

                if (!currProperty.name.equals(storeAsInstance.name())) {
                    currProperty.effectiveName = storeAsInstance.name();
                }
            }
        }

        // Return only processable pairs
        final ArrayList<String> keysToRemove = new ArrayList<String>();
        final ArrayList<String> keysToAlter = new ArrayList<String>();

        for (final Entry<String, PropertyPair> e : propMap.entrySet()) {
            if (!e.getValue().shouldProcess()) {
                keysToRemove.add(e.getKey());
                continue;
            }

            if (!Utility.isNullOrEmpty(e.getValue().effectiveName)) {
                keysToAlter.add(e.getKey());
            }
            else {
                e.getValue().effectiveName = e.getValue().name;
            }
        }

        // remove all entries for keys that should not process
        for (final String key : keysToRemove) {
            propMap.remove(key);
        }

        // Any store as properties should be re-stored into the hash under the efective name.
        for (final String key : keysToAlter) {
            final PropertyPair p = propMap.get(key);
            propMap.remove(key);
            propMap.put(p.effectiveName, p);
        }

        return propMap;
    }

    private Method getter = null;
    private Method setter = null;
    private String name = null;
    Class<?> type = null;
    String effectiveName = null;

    /**
     * Reserved for internal use. Invokes the setter method on the specified instance parameter with the value of the
     * {@link EntityProperty} deserialized as the appropriate type.
     *
     * @param prop
     *            The {@link EntityProperty} containing the value to pass to the setter on the instance.
     * @param instance
     *            An instance of a class supporting this property with getter and setter methods of the
     *            appropriate name and parameter or return type.
     *
     * @throws IllegalArgumentException
     *             if the specified instance parameter is not an instance of the class
     *             or interface declaring the setter method (or of a subclass or implementor thereof).
     * @throws IllegalAccessException
     *             if the setter method is inaccessible.
     * @throws InvocationTargetException
     *             if the setter method throws an exception.
     */
    protected void consumeEntityProperty(final EntityProperty prop, final Object instance)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Class<?> paramType = this.setter.getParameterTypes()[0];
        if (prop.getIsNull()) {
            if (!paramType.isPrimitive()) {
                this.setter.invoke(instance, (Object) null);
            }
        }
        else {
            if (prop.getEdmType() == EdmType.STRING) {
                if (paramType.equals(String.class)) {
                    this.setter.invoke(instance, prop.getValueAsString());
                }
            }
            else if (prop.getEdmType() == EdmType.BINARY) {
                if (paramType.equals(Byte[].class)) {
                    this.setter.invoke(instance, (Object) prop.getValueAsByteObjectArray());
                }
                else if (paramType.equals(byte[].class)) {
                    this.setter.invoke(instance, prop.getValueAsByteArray());
                }
            }
            else if (prop.getEdmType() == EdmType.BOOLEAN) {
                if (paramType.equals(Boolean.class)) {
                    this.setter.invoke(instance, prop.getValueAsBooleanObject());
                }
                else if (paramType.equals(boolean.class)) {
                    this.setter.invoke(instance, prop.getValueAsBoolean());
                }
            }
            else if (prop.getEdmType() == EdmType.DOUBLE) {
                if (paramType.equals(Double.class)) {
                    this.setter.invoke(instance, prop.getValueAsDoubleObject());
                }
                else if (paramType.equals(double.class)) {
                    this.setter.invoke(instance, prop.getValueAsDouble());
                }
            }
            else if (prop.getEdmType() == EdmType.GUID) {
                if (paramType.equals(UUID.class)) {
                    this.setter.invoke(instance, prop.getValueAsUUID());
                }
            }
            else if (prop.getEdmType() == EdmType.INT32) {
                if (paramType.equals(Integer.class)) {
                    this.setter.invoke(instance, prop.getValueAsIntegerObject());
                }
                else if (paramType.equals(int.class)) {
                    this.setter.invoke(instance, prop.getValueAsInteger());
                }
            }
            else if (prop.getEdmType() == EdmType.INT64) {
                if (paramType.equals(Long.class)) {
                    this.setter.invoke(instance, prop.getValueAsLongObject());
                }
                else if (paramType.equals(long.class)) {
                    this.setter.invoke(instance, prop.getValueAsLong());
                }
            }
            else if (prop.getEdmType() == EdmType.DATE_TIME) {
                if (paramType.equals(Date.class)) {
                    this.setter.invoke(instance, prop.getValueAsDate());
                }
            }
            else {
                throw new IllegalArgumentException(String.format(SR.PROPERTY_CANNOT_BE_SERIALIZED_AS_GIVEN_EDMTYPE,
                        this.name, prop.getEdmType().toString()));
            }
        }
    }

    /**
     * Reserved for internal use. Generates an {@link EntityProperty} from the result of invoking the getter method for
     * this property on the specified instance parameter.
     *
     * @param instance
     *            An instance of a class supporting this property with getter and setter methods of the
     *            appropriate name and parameter or return type.
     *
     * @return
     *         An {@link EntityProperty} with the data type and value returned by the invoked getter on the instance.
     *
     * @throws IllegalArgumentException
     *             if the specified instance parameter is not an instance of the class
     *             or interface declaring the getter method (or of a subclass or implementor thereof).
     * @throws IllegalAccessException
     *             if the getter method is inaccessible.
     * @throws InvocationTargetException
     *             if the getter method throws an exception.
     */
    protected EntityProperty generateEntityProperty(final Object instance) throws IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {
        final Class<?> getType = this.getter.getReturnType();
        Object val = this.getter.invoke(instance, (Object[]) null);
        return new EntityProperty(val, getType);
    }

    /**
     * Reserved for internal use. A utility function that returns true if this property is accessible
     * through reflection.
     *
     * @return
     */
    protected boolean shouldProcess() {
        if (Utility.isNullOrEmpty(this.name) || this.getter == null || this.getter.isAnnotationPresent(Ignore.class)
                || this.setter == null || this.setter.isAnnotationPresent(Ignore.class)
                || (!this.getter.getReturnType().equals(this.setter.getParameterTypes()[0]))) {
            return false;
        }

        return true;
    }
}


/**
 * 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.io.IOException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;

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

/**
 * RESERVED FOR INTERNAL USE. The Base Request class for the protocol layer.
 */
public final class BaseRequest {

    private static final String METADATA = "metadata";

    private static final String SERVICE = "service";

    private static final String STATS = "stats";

    private static final String TIMEOUT = "timeout";

    /**
     * Stores the user agent to send over the wire to identify the client.
     */
    private static String userAgent;

    /**
     * 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) {
        if (metadata != null) {
            for (final Entry<String, String> entry : metadata.entrySet()) {
                addMetadata(request, entry.getKey(), entry.getValue(), opContext);
            }
        }
    }

    /**
     * Adds the metadata.
     *
     * @param opContext
     *            an object used to track the execution of the operation
     * @param request
     *            The request.
     * @param name
     *            The metadata name.
     * @param value
     *            The metadata value.
     */
    private static void addMetadata(final HttpURLConnection request, final String name, final String value,
            final OperationContext opContext) {
        if (Utility.isNullOrEmptyOrWhitespace(name)) {
            throw new IllegalArgumentException(SR.METADATA_KEY_INVALID);
        }
        else if (Utility.isNullOrEmptyOrWhitespace(value)) {
            throw new IllegalArgumentException(SR.METADATA_VALUE_INVALID);
        }

        request.setRequestProperty(Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA + name, value);
    }

    /**
     * Adds the optional header.
     *
     * @param request
     *            a HttpURLConnection for the operation.
     * @param name
     *            the metadata name.
     * @param value
     *            the metadata value.
     */
    public static void addOptionalHeader(final HttpURLConnection request, final String name, final String value) {
        if (value != null && !value.equals(Constants.EMPTY_STRING)) {
            request.setRequestProperty(name, value);
        }
    }

    /**
     * Creates the specified resource. Note request is set to setFixedLengthStreamingMode(0); Sign with 0 length.
     *
     * @param uri
     *            the request Uri.
     * @param options
     *            A {@link RequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation.
     * @param builder
     *            the UriQueryBuilder for the request
     * @param opContext
     *            an object used to track the execution of the operation
     *
     * @return a HttpURLConnection to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if there is an improperly formated URI
     * @throws StorageException
     * @throws IllegalArgumentException
     */
    public static HttpURLConnection create(final URI uri, final RequestOptions options, UriQueryBuilder builder,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
        retConnection.setFixedLengthStreamingMode(0);
        retConnection.setDoOutput(true);
        retConnection.setRequestMethod(Constants.HTTP_PUT);

        return retConnection;
    }

    /**
     * Creates the web request.
     *
     * @param uri
     *            the request Uri.
     * @param options
     *            A {@link RequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. This parameter is unused.
     * @param builder
     *            the UriQueryBuilder for the request
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a HttpURLConnection to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if there is an improperly formated URI
     * @throws StorageException
     */
    public static HttpURLConnection createURLConnection(final URI uri, final RequestOptions options,
            UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
            StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        if (options.getTimeoutIntervalInMs() != null && options.getTimeoutIntervalInMs() != 0) {
            builder.add(TIMEOUT, String.valueOf(options.getTimeoutIntervalInMs() / 1000));
        }

        final URL resourceUrl = builder.addToURI(uri).toURL();

        // Get the proxy settings
        Proxy proxy = OperationContext.getDefaultProxy();
        if (opContext != null && opContext.getProxy() != null) {
            proxy = opContext.getProxy();
        }

        // Set up connection, optionally with proxy settings
        final HttpURLConnection retConnection;
        if (proxy != null) {
            retConnection = (HttpURLConnection) resourceUrl.openConnection(proxy);
        }
        else {
            retConnection = (HttpURLConnection) resourceUrl.openConnection();
        }

        /*
         * ReadTimeout must be explicitly set to avoid a bug in JDK 6. In certain cases, this bug causes an immediate
         * read timeout exception to be thrown even if ReadTimeout is not set.
         *
         * Both connect and read timeout are set to the same value as we have no way of knowing how to partition
         * the remaining time between these operations. The user can override these timeouts using the SendingRequest
         * event handler if more control is desired.
         */
        int timeout = Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs(), options.getTimeoutIntervalInMs());
        retConnection.setReadTimeout(timeout);
        retConnection.setConnectTimeout(timeout);

        // Note : by default sends Accept behavior as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
        retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, Constants.HeaderConstants.XML_TYPE);
        retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, Constants.UTF8_CHARSET);

        // Note: by default sends Accept-Encoding as gzip
        retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_ENCODING, Constants.EMPTY_STRING);

        // Note : by default sends Content-type behavior as application/x-www-form-urlencoded for posts.
        retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, Constants.EMPTY_STRING);

        retConnection.setRequestProperty(Constants.HeaderConstants.STORAGE_VERSION_HEADER,
                Constants.HeaderConstants.TARGET_STORAGE_VERSION);
        retConnection.setRequestProperty(Constants.HeaderConstants.USER_AGENT, getUserAgent());
        retConnection.setRequestProperty(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER,
                opContext.getClientRequestID());

        return retConnection;
    }

    /**
     * Deletes the specified resource. Sign with no length specified.
     *
     * @param uri
     *            the request Uri.
     * @param timeout
     *            the timeout for the request
     * @param builder
     *            the UriQueryBuilder for the request
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a HttpURLConnection to perform the operation.
     * @throws IOException
     *             if there is an error opening the connection
     * @throws URISyntaxException
     *             if there is an improperly formated URI
     * @throws StorageException
     */
    public static HttpURLConnection delete(final URI uri, final RequestOptions options, UriQueryBuilder builder,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
        retConnection.setRequestMethod(Constants.HTTP_DELETE);

        return retConnection;
    }

    /**
     * Gets a {@link UriQueryBuilder} for listing.
     *
     * @param listingContext
     *            A {@link ListingContext} object that specifies parameters for
     *            the listing operation, if any. May be null.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    public static UriQueryBuilder getListUriQueryBuilder(final ListingContext listingContext) throws StorageException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST);

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

            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());
            }
        }

        return builder;
    }

    /**
     * Gets the properties. Sign with no length specified.
     *
     * @param uri
     *            The Uri to query.
     * @param timeout
     *            The timeout.
     * @param builder
     *            The builder.
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a web request for performing the operation.
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * */
    public static HttpURLConnection getProperties(final URI uri, final RequestOptions options, UriQueryBuilder builder,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
        retConnection.setRequestMethod(Constants.HTTP_HEAD);

        return retConnection;
    }

    /**
     * Creates a HttpURLConnection used to retrieve the Analytics service properties from the storage service.
     *
     * @param uri
     *            The service endpoint.
     * @param timeout
     *            The timeout.
     * @param builder
     *            The builder.
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a web request for performing the operation.
     * @throws IOException
     * @throws URISyntaxException
     * @throws StorageException
     */
    public static HttpURLConnection getServiceProperties(final URI uri, final RequestOptions options,
            UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
            StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
        builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);

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

        return retConnection;
    }

    /**
     * Creates a web request to get the stats of the service.
     *
     * @param uri
     *            The service endpoint.
     * @param timeout
     *            The timeout.
     * @param builder
     *            The builder.
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a web request for performing the operation.
     * @throws IOException
     * @throws URISyntaxException
     * @throws StorageException
     */
    public static HttpURLConnection getServiceStats(final URI uri, final RequestOptions options,
            UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
            StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        builder.add(Constants.QueryConstants.COMPONENT, STATS);
        builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);

        final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
        retConnection.setRequestMethod("GET");

        return retConnection;
    }

    /**
     * Gets the user agent to send over the wire to identify the client.
     *
     * @return the user agent to send over the wire to identify the client.
     */
    public static String getUserAgent() {
        if (userAgent == null) {
            String userAgentComment = String.format(Utility.LOCALE_US, "(Android %s; %s; %s)",
                    android.os.Build.VERSION.RELEASE, android.os.Build.BRAND, android.os.Build.MODEL);
            userAgent = String.format("%s/%s %s", Constants.HeaderConstants.USER_AGENT_PREFIX,
                    Constants.HeaderConstants.USER_AGENT_VERSION, userAgentComment);
        }

        return userAgent;
    }

    /**
     * Sets the metadata. Sign with 0 length.
     *
     * @param uri
     *            The blob Uri.
     * @param timeout
     *            The timeout.
     * @param builder
     *            The builder.
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a web request for performing the operation.
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * */
    public static HttpURLConnection setMetadata(final URI uri, final RequestOptions options, UriQueryBuilder builder,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {

        if (builder == null) {
            builder = new UriQueryBuilder();
        }

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

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

        return retConnection;
    }

    /**
     * Creates a HttpURLConnection used to set the Analytics service properties on the storage service.
     *
     * @param uri
     *            The service endpoint.
     * @param timeout
     *            The timeout.
     * @param builder
     *            The builder.
     * @param opContext
     *            an object used to track the execution of the operation
     * @return a web request for performing the operation.
     * @throws IOException
     * @throws URISyntaxException
     * @throws StorageException
     */
    public static HttpURLConnection setServiceProperties(final URI uri, final RequestOptions options,
            UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
            StorageException {
        if (builder == null) {
            builder = new UriQueryBuilder();
        }

        builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
        builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);

        final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);

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

        return retConnection;
    }

    /**
     * A private default constructor. All methods of this class are static so no instances of it should ever be created.
     */
    private BaseRequest() {
        // 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

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

/**
 * The class is an append-only stream for writing into storage.
 */
public class FileOutputStream extends OutputStream {

    /**
     * Holds the reference to the file this stream is associated with.
     */
    private final CloudFile parentFileRef;

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

    /**
     * Holds the lock for synchronized access to the last error.
     */
    Object lastErrorLock = new Object();

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

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

    /**
     * Holds the options for the current stream.
     */
    FileRequestOptions options;

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

    /**
     * Holds the currentOffset the stream is writing to.
     */
    private long currentOffset;

    /**
     * A private buffer to store data prior to committing to the cloud.
     */
    private ByteArrayOutputStream outBuffer;

    /**
     * Holds the number of currently buffered bytes.
     */
    private int currentBufferedBytes;

    /**
     * Holds the write threshold of number of bytes to buffer prior to dispatching a write.
     */
    private int internalWriteThreshold = -1;

    /**
     * Holds the number of current outstanding requests.
     */
    private volatile int outstandingRequests;

    /**
     * The ExecutorService used to schedule tasks for this stream.
     */
    private final ExecutorService threadExecutor;

    /**
     * The CompletionService used to await task completion for this stream.
     */
    private final ExecutorCompletionService<Void> completionService;

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

    /**
     * Initializes a new instance of the FileOutputStream class.
     *
     * @param parentFile
     *            A {@link CloudFile} object which represents the file that this stream is associated with.
     * @param length
     *            A long which represents the length of the file in bytes.
     * @param accessCondition
     *            An {@link AccessCondition} object which represents the access conditions for the file.
     * @param options
     *            A {@link FileRequestOptions} object which 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 FileOutputStream(final CloudFile parentFile, final long length, final AccessCondition accessCondition,
            final FileRequestOptions options, final OperationContext opContext) throws StorageException {
        this.accessCondition = accessCondition;
        this.parentFileRef = parentFile;
        this.options = new FileRequestOptions(options);
        this.outBuffer = new ByteArrayOutputStream();
        this.opContext = opContext;
        this.streamFaulted = false;

        if (this.options.getConcurrentRequestCount() < 1) {
            throw new IllegalArgumentException("ConcurrentRequestCount");
        }

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

        // V2 cachedThreadPool for perf.
        this.threadExecutor = Executors.newFixedThreadPool(this.options.getConcurrentRequestCount());
        this.completionService = new ExecutorCompletionService<Void>(this.threadExecutor);
        this.internalWriteThreshold = (int) Math.min(this.parentFileRef.getStreamWriteSizeInBytes(), length);
    }

    /**
     * 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 void checkStreamState() throws IOException {
        synchronized (this.lastErrorLock) {
            if (this.streamFaulted) {
                throw this.lastError;
            }
        }
    }

    /**
     * Closes this output stream and releases any system resources associated with this stream. If any data remains in
     * the buffer it is committed to the service.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @Override
    @DoesServiceRequest
    public void close() throws IOException {
        try {
            // if the user has already closed the stream, this will throw a STREAM_CLOSED exception
            // if an exception was thrown by any thread in the threadExecutor, realize it now
            this.checkStreamState();

            // flush any remaining data
            this.flush();

            // shut down the ExecutorService.
            this.threadExecutor.shutdown();

            // try to commit the file
            try {
                this.commit();
            }
            catch (final StorageException e) {
                throw Utility.initIOException(e);
            } catch (URISyntaxException e) {
                throw Utility.initIOException(e);
            }
        }
        finally {
            // if close() is called again, an exception will be thrown
            synchronized (this.lastErrorLock) {
                this.streamFaulted = true;
                this.lastError = new IOException(SR.STREAM_CLOSED);
            }

            // if an exception was thrown and the executor was not yet closed, call shutDownNow() to cancel all tasks
            // and shutdown the ExecutorService
            if (!this.threadExecutor.isShutdown()) {
                this.threadExecutor.shutdownNow();
            }
        }
    }

    /**
     * Commits the file.
     *
     * @throws StorageException
     *             An exception representing any error which occurred during the operation.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    private void commit() throws StorageException, URISyntaxException {
        if (this.options.getStoreFileContentMD5()) {
            this.parentFileRef.getProperties().setContentMD5(Base64.encode(this.md5Digest.digest()));
        }

        this.parentFileRef.uploadProperties(this.accessCondition, this.options, this.opContext);
    }

    /**
     * Dispatches a write operation for a given length.
     *
     * @param writeLength
     *            An int which represents the length of the data to write, this is the write threshold that
     *            triggered the write.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    @DoesServiceRequest
    private synchronized void dispatchWrite(final int writeLength) throws IOException {
        if (writeLength == 0) {
            return;
        }

        Callable<Void> worker = null;

        if (this.outstandingRequests > this.options.getConcurrentRequestCount() * 2) {
            this.waitForTaskToComplete();
        }

        final ByteArrayInputStream bufferRef = new ByteArrayInputStream(this.outBuffer.toByteArray());
        final CloudFile fileRef = this.parentFileRef;
        long tempOffset = this.currentOffset;
        long tempLength = writeLength;

        final long opWriteLength = tempLength;
        final long opOffset = tempOffset;
        this.currentOffset += writeLength;

        worker = new Callable<Void>() {
            @Override
            public Void call() {
                try {
                    fileRef.uploadRange(bufferRef, opOffset, opWriteLength, FileOutputStream.this.accessCondition,
                            FileOutputStream.this.options, FileOutputStream.this.opContext);
                }
                catch (final IOException e) {
                    synchronized (FileOutputStream.this.lastErrorLock) {
                        FileOutputStream.this.streamFaulted = true;
                        FileOutputStream.this.lastError = e;
                    }
                }
                catch (final StorageException e) {
                    synchronized (FileOutputStream.this.lastErrorLock) {
                        FileOutputStream.this.streamFaulted = true;
                        FileOutputStream.this.lastError = Utility.initIOException(e);
                    }
                } catch (URISyntaxException e) {
                    synchronized (FileOutputStream.this.lastErrorLock) {
                        FileOutputStream.this.streamFaulted = true;
                        FileOutputStream.this.lastError = Utility.initIOException(e);
                    }
                }

                return null;
            }
        };

        // Do work and reset buffer.
        this.completionService.submit(worker);
        this.outstandingRequests++;
        this.currentBufferedBytes = 0;
        this.outBuffer = new ByteArrayOutputStream();
    }

    /**
     * Flushes this output stream and forces any buffered output bytes to be written out. If any data remains in the
     * buffer it is committed to the service.
     *
     * @throws IOException
     *             If an I/O error occurs.
     */
    @Override
    @DoesServiceRequest
    public synchronized void flush() throws IOException {
        this.checkStreamState();

        // Dispatch a write for the current bytes in the buffer
        this.dispatchWrite(this.currentBufferedBytes);

        // Waits for all submitted tasks to complete
        while (this.outstandingRequests > 0) {
            // Wait for a task to complete
            this.waitForTaskToComplete();

            // If that task threw an error, fail fast
            this.checkStreamState();
        }
    }

    /**
     * Waits for one task to complete
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    private void waitForTaskToComplete() throws IOException {
        try {
            final Future<Void> future = this.completionService.take();
            future.get();
        }
        catch (final InterruptedException e) {
            throw Utility.initIOException(e);
        }
        catch (final ExecutionException e) {
            throw Utility.initIOException(e);
        }

        this.outstandingRequests--;
    }

    /**
     * Writes b.length bytes from the specified byte array to this output stream.
     *
     * @param data
     *            A byte array which represents the data to write.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    @Override
    @DoesServiceRequest
    public void write(final byte[] data) throws IOException {
        this.write(data, 0, data.length);
    }

    /**
     * Writes length bytes from the specified byte array starting at offset to this output stream.
     *
     * @param data
     *            A byte array which represents the data to write.
     * @param offset
     *            An int which represents the start offset in the data.
     * @param length
     *            An int which represents the number of bytes to write.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    @Override
    @DoesServiceRequest
    public void write(final byte[] data, final int offset, final int length) throws IOException {
        if (offset < 0 || length < 0 || length > data.length - offset) {
            throw new IndexOutOfBoundsException();
        }

        this.writeInternal(data, offset, length);
    }

    /**
     * Writes all data from the InputStream to the File.
     *
     * @param sourceStream
     *            An {@link InputStream} object which species the data to write to the File.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     * @throws StorageException
     *             An exception representing any error which occurred during the operation.
     */
    @DoesServiceRequest
    public void write(final InputStream sourceStream, final long writeLength) throws IOException, StorageException {
        Utility.writeToOutputStream(sourceStream, this, writeLength, false, false, this.opContext, this.options, false);
    }

    /**
     * Writes the specified byte to this output stream. The general contract for write is that one byte is written to
     * the output stream. The byte to be written is the eight low-order bits of the argument b. The 24 high-order bits
     * of b are ignored.
     *
     * @param byteVal
     *            An int which represents the bye value to write.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */
    @Override
    @DoesServiceRequest
    public void write(final int byteVal) throws IOException {
        this.write(new byte[] { (byte) (byteVal & 0xFF) });
    }

    /**
     * Writes the data to the buffer and triggers writes to the service as needed.
     *
     * @param data
     *            A byte array which represents the data to write.
     * @param offset
     *            An int which represents the start offset in the data.
     * @param length
     *            An int which represents the number of bytes to write.
     *
     * @throws IOException
     *             If an I/O error occurs. In particular, an IOException may be thrown if the output stream has been
     *             closed.
     */

    @DoesServiceRequest
    private synchronized void writeInternal(final byte[] data, int offset, int length) throws IOException {
        while (length > 0) {
            this.checkStreamState();

            final int availableBufferBytes = this.internalWriteThreshold - this.currentBufferedBytes;
            final int nextWrite = Math.min(availableBufferBytes, length);

            // If we need to set MD5 then update the digest accordingly
            if (this.options.getStoreFileContentMD5()) {
                this.md5Digest.update(data, offset, nextWrite);
            }

            this.outBuffer.write(data, offset, nextWrite);
            this.currentBufferedBytes += nextWrite;
            offset += nextWrite;
            length -= nextWrite;

            if (this.currentBufferedBytes == this.internalWriteThreshold) {
                this.dispatchWrite(this.internalWriteThreshold);
            }
        }
    }
}


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

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

/**
 * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access
 * signature.
 */
public final class SharedAccessFilePolicy extends SharedAccessPolicy {
    /**
     * The permissions for a shared access signature associated with this shared access policy.
     */
    private EnumSet<SharedAccessFilePermissions> permissions;

    /**
     * Gets the permissions for a shared access signature associated with this shared access policy.
     *
     * @return A java.util.EnumSet object that contains {@link SharedAccessFilePermissions} values that
     *         represents the set of shared access permissions.
     */
    public EnumSet<SharedAccessFilePermissions> getPermissions() {
        return this.permissions;
    }

    /**
     * Sets the permissions for a shared access signature associated with this shared access policy.
     *
     * @param permissions
     *            The permissions, represented by a java.util.EnumSet object that contains
     *            {@link SharedAccessFilePermissions} values, to set for the shared access signature.
     */
    public void setPermissions(final EnumSet<SharedAccessFilePermissions> permissions) {
        this.permissions = permissions;
    }

    /**
     * Converts this policy's permissions to a string.
     *
     * @return A String that represents the shared access permissions in the "rcwdl" format,
     *         which is described at {@link #setPermissionsFromString(String)}.
     */
    @Override
    public String permissionsToString() {
        if (this.permissions == null) {
            return Constants.EMPTY_STRING;
        }

        // The service supports a fixed order => rcwdl
        final StringBuilder builder = new StringBuilder();

        if (this.permissions.contains(SharedAccessFilePermissions.READ)) {
            builder.append("r");
        }

        if (this.permissions.contains(SharedAccessFilePermissions.CREATE)) {
            builder.append("c");
        }

        if (this.permissions.contains(SharedAccessFilePermissions.WRITE)) {
            builder.append("w");
        }

        if (this.permissions.contains(SharedAccessFilePermissions.DELETE)) {
            builder.append("d");
        }

        if (this.permissions.contains(SharedAccessFilePermissions.LIST)) {
            builder.append("l");
        }

        return builder.toString();
    }

    /**
     * Sets shared access permissions using the specified permissions string.
     *
     * @param value
     *            A String that represents the shared access permissions. The string must contain one or
     *            more of the following values. Note they must all be lowercase.
     *

     *
r: Read access.

     *
c: Create access.

     *
w: Write access.

     *
d: Delete access.

     *
l: List access.

     *

     */
    public void setPermissionsFromString(final String value) {
        EnumSet<SharedAccessFilePermissions> initial = EnumSet.noneOf(SharedAccessFilePermissions.class);
        for (final char c : value.toCharArray()) {
            switch (c) {
                case 'r':
                    initial.add(SharedAccessFilePermissions.READ);
                    break;
                case 'c':
                    initial.add(SharedAccessFilePermissions.CREATE);
                    break;
                case 'w':
                    initial.add(SharedAccessFilePermissions.WRITE);
                    break;
                case 'd':
                    initial.add(SharedAccessFilePermissions.DELETE);
                    break;
                case 'l':
                    initial.add(SharedAccessFilePermissions.LIST);
                    break;
                default:
                    throw new IllegalArgumentException("value");
            }
        }

        this.permissions = initial;
    }
}