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

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Locale;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.LoggingOperations;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageLocation;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.blob.BlobListingDetails;
import com.microsoft.azure.storage.blob.BlobRequestOptions;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobDirectory;
import com.microsoft.azure.storage.blob.ListBlobItem;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;
import com.microsoft.azure.storage.table.CloudTable;
import com.microsoft.azure.storage.table.CloudTableClient;

/**
 * Provides a client-side logical representation for Microsoft Azure Storage Analytics. This client is used to configure
 * and execute storage analytics requests.
 *

 * The service client encapsulates the endpoint or endpoints for the blob and table service. It also encapsulates the
 * credentials for accessing the storage account.
 */
public class CloudAnalyticsClient {

    /**
     * The blob client for logging features.
     */
    protected final CloudBlobClient blobClient;

    /**
     * The table client for metrics features.
     */
    protected final CloudTableClient tableClient;

    /**
     * The container in which to look for logs.
     */
    protected String LogContainer = Constants.AnalyticsConstants.LOGS_CONTAINER;

    /**
     * Initializes a new instance of the CloudAnalyticsClient class using the specified blob and table
     * service endpoints and account credentials.
     *
     * @param blobStorageUri
     *            A {@link StorageUri} object containing the Blob service endpoint to use to create the client.
     * @param tableStorageUri
     *            A {@link StorageUri} object containing the Table service endpoint to use to create the client.
     * @param credentials
     *            A {@link StorageCredentials} object.
     */
    public CloudAnalyticsClient(StorageUri blobStorageUri, StorageUri tableStorageUri, StorageCredentials credentials) {
        Utility.assertNotNull("blobStorageUri", blobStorageUri);
        Utility.assertNotNull("tableStorageUri", tableStorageUri);

        this.blobClient = new CloudBlobClient(blobStorageUri, credentials);
        this.tableClient = new CloudTableClient(tableStorageUri, credentials);
    }

    /**
     * Gets the {@link CloudBlobDirectory} object for the logs for a specific storage service.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @return
     *         A {@link CloudBlobDirectory} object.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudBlobDirectory getLogDirectory(StorageService service) throws URISyntaxException, StorageException {
        Utility.assertNotNull("service", service);
        return this.blobClient.getContainerReference(this.LogContainer).getDirectoryReference(
                service.toString().toLowerCase(Locale.US));
    }

    /**
     * Gets the hour metrics table for a specific storage service.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @return
     *         The {@link CloudTable} object for the storage service.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudTable getHourMetricsTable(StorageService service) throws URISyntaxException, StorageException {
        return this.getHourMetricsTable(service, null);
    }

    /**
     * Gets the hour metrics table for a specific storage service.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @param location
     *            A {@link StorageLocation} enumeration value that indicates which storage location to use.
     * @return
     *         The {@link CloudTable} object for the storage service.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudTable getHourMetricsTable(StorageService service, StorageLocation location) throws URISyntaxException,
            StorageException {
        Utility.assertNotNull("service", service);
        if (location == null) {
            location = StorageLocation.PRIMARY;
        }

        switch (service) {
            case BLOB:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_BLOB);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_BLOB);
                }

            case FILE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_FILE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_FILE);
                }

            case QUEUE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_QUEUE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_QUEUE);
                }

            case TABLE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_TABLE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_TABLE);
                }

            default:
                throw new IllegalArgumentException(SR.INVALID_STORAGE_SERVICE);
        }
    }

    /**
     * Gets the minute metrics table for a specific storage service.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @return
     *         The {@link CloudTable} object for the storage service.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudTable getMinuteMetricsTable(StorageService service) throws URISyntaxException, StorageException {
        return this.getMinuteMetricsTable(service, null);
    }

    /**
     * Gets the minute metrics table for a specific storage service.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @param location
     *            A {@link StorageLocation} enumeration value that indicates which storage location to use.
     * @return
     *         The CloudTable object for the storage service.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudTable getMinuteMetricsTable(StorageService service, StorageLocation location)
            throws URISyntaxException, StorageException {
        Utility.assertNotNull("service", service);
        if (location == null) {
            location = StorageLocation.PRIMARY;
        }

        switch (service) {
            case BLOB:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_BLOB);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_BLOB);
                }
            case FILE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_FILE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_FILE);
                }
            case QUEUE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_QUEUE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_QUEUE);
                }
            case TABLE:
                if (location == StorageLocation.PRIMARY) {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_TABLE);
                }
                else {
                    return this.tableClient.getTableReference(
                            Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_TABLE);
                }
            default:
                throw new IllegalArgumentException(SR.INVALID_STORAGE_SERVICE);
        }
    }

    /**
     * Gets the capacity metrics table for the blob service.
     *
     * @return
     *         A {@link CloudTable} object.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public CloudTable getCapacityTable() throws URISyntaxException, StorageException {
        return this.tableClient.getTableReference(Constants.AnalyticsConstants.METRICS_CAPACITY_BLOB);
    }

    /**
     * Returns an enumerable collection of log blobs, retrieved lazily.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @return
     *         An enumerable collection of objects that implement ListBlobItem and are retrieved lazily.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public Iterable<ListBlobItem> listLogBlobs(StorageService service) throws URISyntaxException, StorageException {
        return this.listLogBlobs(service, null /* startTime */, null /* endTime */, null /* operations */,
                null /* details */, null /* options */, null /* operationContext */);
    }

    /**
     * Returns an enumerable collection of log blobs, retrieved lazily.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @param startTime
     *            A java.util.Date object representing the start of the time range for which logs should
     *            be retrieved.
     * @param endTime
     *            A java.util.Date object representing the end of the time range for which logs should
     *            be retrieved.
     * @param operations
     *            A {@link LoggingOperations} enumeration set that indicates which log types to return.
     * @param details
     *            A {@link BlobListingDetails} enumeration set that indicates whether or not blob metadata should
     *            be returned. None or METADATA are the only valid values.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies additional options for the request.
     * @param operationContext
     *            An {@link OperationContext} object that represents the context for the current operation.
     * @return
     *         An enumerable collection of objects that implement {@link ListBlobItem} and are retrieved lazily.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public Iterable<ListBlobItem> listLogBlobs(StorageService service, Date startTime, Date endTime,
            EnumSet<LoggingOperations> operations, BlobListingDetails details, BlobRequestOptions options,
            OperationContext operationContext) throws StorageException, URISyntaxException {
        Utility.assertNotNull("service", service);
        if (operations == null) {
            operations = EnumSet.allOf(LoggingOperations.class);
        }

        if (!(details == null || details.equals(BlobListingDetails.METADATA))) {
            throw new IllegalArgumentException(SR.INVALID_LISTING_DETAILS);
        }

        if (operations.equals(EnumSet.noneOf(LoggingOperations.class))) {
            throw new IllegalArgumentException(SR.INVALID_LOGGING_LEVEL);
        }

        EnumSet<BlobListingDetails> metadataDetails;
        if (details != null
                && (details.equals(BlobListingDetails.METADATA) || !operations.equals(EnumSet
                        .allOf(LoggingOperations.class)))) {
            metadataDetails = EnumSet.of(BlobListingDetails.METADATA);
        }
        else {
            metadataDetails = EnumSet.noneOf(BlobListingDetails.class);
        }

        return new LogBlobIterable(this.getLogDirectory(service), startTime, endTime, operations, metadataDetails,
                options, operationContext);
    }

    /**
     * Returns an enumerable collection of log records, retrieved lazily.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @return
     *         An enumerable collection of objects that implement ListBlobItem and are retrieved lazily.
     * @throws URISyntaxException
     * @throws StorageException
     */
    public Iterable<LogRecord> listLogRecords(StorageService service) throws URISyntaxException, StorageException {
        return this
                .listLogRecords(service, null /* startTime */, null /* endTime */, null /* options */, null /* operationContext */);
    }

    /**
     * Returns an enumerable collection of log records, retrieved lazily.
     *
     * @param service
     *            A {@link StorageService} enumeration value that indicates which storage service to use.
     * @param startTime
     *            A java.util.Date object representing the start of the time range for which logs should
     *            be retrieved.
     * @param endTime
     *            A java.util.Date object representing the end of the time range for which logs should
     *            be retrieved.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies additional options for the request.
     * @param operationContext
     *            An {@link OperationContext} object that represents the context for the current operation.
     * @return
     *         An enumerable collection of objects that implement {@link ListBlobItem} and are retrieved lazily.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public Iterable<LogRecord> listLogRecords(StorageService service, Date startTime, Date endTime,
            BlobRequestOptions options, OperationContext operationContext) throws StorageException, URISyntaxException {
        Utility.assertNotNull("service", service);
        EnumSet<LoggingOperations> operations = EnumSet.allOf(LoggingOperations.class);
        EnumSet<BlobListingDetails> metadataDetails = EnumSet.noneOf(BlobListingDetails.class);
        Iterator<ListBlobItem> blobIterator = new LogBlobIterable(this.getLogDirectory(service), startTime, endTime,
                operations, metadataDetails, options, operationContext).iterator();

        return new LogRecordIterable(blobIterator);
    }

    /**
     * Returns an enumerable collection of log records, retrieved lazily.
     *
     * @param logBlobs
     *            An {@link Iterable} of blobs to parse LogRecords from.
     * @return
     *         An enumerable collection of objects that implement {@link LogRecords} and are retrieved lazily.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public static Iterable<LogRecord> parseLogBlobs(Iterable<ListBlobItem> logBlobs) {
        Utility.assertNotNull("logBlobs", logBlobs);

        return new LogRecordIterable(logBlobs.iterator());
    }

    /**
     * Returns an enumerable collection of log records, retrieved lazily.
     *
     * @param logBlobs
     *            An {@link Iterable} of blobs to parse LogRecords from.
     * @return
     *         An enumerable collection of objects that implement {@link LogRecords} and are retrieved lazily.
     * @throws StorageException
     * @throws URISyntaxException
     */
    public static Iterable<LogRecord> parseLogBlob(ListBlobItem logBlob) {
        Utility.assertNotNull("logBlob", logBlob);
        ArrayList<ListBlobItem> blobWrapper = new ArrayList<ListBlobItem>();
        blobWrapper.add(logBlob);

        return new LogRecordIterable(blobWrapper.iterator());
    }
}


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

/**
 * Represents a set of timeout, payload format, and retry policy options that may be specified for a table operation
 * request.
 */
public class TableRequestOptions extends RequestOptions {

    /**
     * The interface whose function is used to get the <see cref="EdmType"/> for an entity property
     * given the partition key, row, key, and the property name, if the interface is implemented
     */
    public interface PropertyResolver {

        /**
         * Given the partition key, row, key, and the property name, produces the EdmType
         *
         * @param pk
         *            A String which represents the partition key.
         * @param rk
         *            A String which represents the row key.
         * @param key
         *            A String which represents the property name.
         * @param value
         *            A String which represents the property value.
         * @return
         *         The {@link EdmType} of the property.
         */
        public EdmType propertyResolver(String pk, String rk, String key, String value);

    }

    /**
     * The interface whose function is used to get the <see cref="EdmType"/> for an entity property
     * given the partition key, row, key, and the property name, if the interface is implemented
     */
    private PropertyResolver propertyResolver;

    /**
     * The {@link TablePayloadFormat} that is used for any table accessed with this {@link TableRequest} object.
     *
     * Default is Json Minimal Metadata.
     */
    private TablePayloadFormat payloadFormat;

    /**
     * Flag that specifies whether the client should look to correct Date values stored on a {@link TableEntity}
     * that may have been written using versions of this library prior to 0.4.0.
     * See here for more details.
     */
    private Boolean dateBackwardCompatibility;

    /**
     * Creates an instance of the TableRequestOptions
     */
    public TableRequestOptions() {
        super();
    }

    /**
     * Creates an instance of the RequestOptions class by copying values from another
     * TableRequestOptions instance.
     *
     * @param other
     *            A TableRequestOptions object that represents the request options to copy.
     */
    public TableRequestOptions(final TableRequestOptions other) {
        super(other);
        if (other != null) {
            this.setTablePayloadFormat(other.getTablePayloadFormat());
            this.setPropertyResolver(other.getPropertyResolver());
            this.setDateBackwardCompatibility(other.getDateBackwardCompatibility());
        }
    }

    /**
     * Initializes the values for this TableRequestOptions instance, if they are currently
     * null, using the values specified in the {@link CloudTableClient} parameter.
     *
     * @param options
     *            A {@link TableRequestOptions} object which represents the input options to copy from when applying
     *            defaults.
     * @param client
     *            A {@link CloudTableClient} object from which to copy the timeout and retry policy.
     *
     * @return A {@link TableRequestOptions} object.
     *
     */
    protected static final TableRequestOptions populateAndApplyDefaults(final TableRequestOptions options,
            final CloudTableClient client) {
        TableRequestOptions modifiedOptions = new TableRequestOptions(options);
        TableRequestOptions.populate(modifiedOptions, client.getDefaultRequestOptions());
        TableRequestOptions.applyDefaults(modifiedOptions);
        return modifiedOptions;
    }

    /**
     * Applies defaults to the options passed in.
     *
     * @param modifiedOptions
     *          The options to apply defaults to.
     */
    protected static void applyDefaults(final TableRequestOptions modifiedOptions) {
        Utility.assertNotNull("modifiedOptions", modifiedOptions);
        RequestOptions.applyBaseDefaultsInternal(modifiedOptions);
        if (modifiedOptions.getTablePayloadFormat() == null) {
            modifiedOptions.setTablePayloadFormat(TablePayloadFormat.Json);
        }

        if (modifiedOptions.getDateBackwardCompatibility() == null) {
            modifiedOptions.setDateBackwardCompatibility(false);
        }
    }

    /**
     * Populates any null fields in the first requestOptions object with values from the second requestOptions object.
     *
     * @param modifiedOptions
     *            A {@link TableRequestOptions} object from which to copy options.
     * @param clientOptions
     *            A {@link TableRequestOptions} object where options will be copied.
     *
     * @return A {@link RequestOptions} object.
     */
    private static void populate(TableRequestOptions modifiedOptions, final TableRequestOptions clientOptions) {
        RequestOptions.populateRequestOptions(modifiedOptions, clientOptions, true /* setStartTime */);
        if (modifiedOptions.getTablePayloadFormat() == null) {
            modifiedOptions.setTablePayloadFormat(clientOptions.getTablePayloadFormat());
        }

        if (modifiedOptions.getPropertyResolver() == null) {
            modifiedOptions.setPropertyResolver(clientOptions.getPropertyResolver());
        }

        if (modifiedOptions.getDateBackwardCompatibility() == null) {
            modifiedOptions.setDateBackwardCompatibility(clientOptions.getDateBackwardCompatibility());
        }
    }

    /**
     * Gets the {@link TablePayloadFormat} to be used. For more information about {@link TablePayloadFormat} defaults,
     * see {@link #setTablePayloadFormat(TablePayloadFormat)}.
     *
     * @return
     *         The {@link TablePayloadFormat} used by this {@link TableRequest}.
     */
    public TablePayloadFormat getTablePayloadFormat() {
        return this.payloadFormat;
    }

    /**
     * Gets the interface that contains a function which is used to get the <see cref="EdmType"/> for an entity property
     * given the partition key, row, key, and the property name. For more information about the {@link PropertyResolver}
     * defaults, see {@link #setPropertyResolver(PropertyResolver)}.
     *
     * @return
     *         The current {@link PropertyResolver} object.
     */
    public PropertyResolver getPropertyResolver() {
        return this.propertyResolver;
    }

    /**
     * Gets whether the client should look to correct Date values stored on a {@link TableEntity}
     * that may have been written using versions of this library prior to 0.4.0,
     * see {@link #setDateBackwardCompatibility(Boolean)}.
     *

     * See here for more details.
     *
     * @return
     *        true if dateBackwardCompatibility is enabled; otherwise, false
     */
    public Boolean getDateBackwardCompatibility() {
        return this.dateBackwardCompatibility;
    }

    /**
     * Sets the {@link TablePayloadFormat} to be used.
     *

     * The default {@link TablePayloadFormat} is set in the client and is by default {@link TablePayloadFormat#Json}.
     * You can change the {@link TablePayloadFormat} on this request by setting this property. You can also change the
     * value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all subsequent requests made
     * via the service client will use that {@link TablePayloadFormat}.
     *
     * @param payloadFormat
     *            Specifies the {@link TablePayloadFormat} to set.
     */
    public void setTablePayloadFormat(TablePayloadFormat payloadFormat) {
        this.payloadFormat = payloadFormat;
    }

    /**
     * Sets the interface that contains a function which is used to get the <see cref="EdmType"/> for an entity property
     * given the partition key, row, key, and the property name.
     *

     * The default {@link PropertyResolver} is set in the client and is by default null, indicating not to use a
     * property resolver. You can change the {@link PropertyResolver} on this request by setting this property. You can
     * also change the value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all subsequent
     * requests made via the service client will use that {@link PropertyResolver}.
     *
     * @param propertyResolver
     *            Specifies the {@link PropertyResolver} to set.
     */
    public void setPropertyResolver(PropertyResolver propertyResolver) {
        this.propertyResolver = propertyResolver;
    }

    /**
     * Sets whether the client should look to correct Date values stored on a {@link TableEntity}
     * that may have been written using versions of this library prior to 0.4.0.
     *

     * {@link #dateBackwardCompatibility} is by default false, indicating a post 0.4.0 version or mixed-
     * platform usage. You can change the {@link #dateBackwardCompatibility} on this request by setting this property.
     * You can also change the value on the {@link CloudTableClient#getDefaultRequestOptions()} object so that all
     * subsequent requests made via the service client will use that {@link #dateBackwardCompatibility}.
     *

     * See here for more details.
     *
     * @param dateBackwardCompatibility
     *        true to enable dateBackwardCompatibility; otherwise, false
     */
    public void setDateBackwardCompatibility(Boolean dateBackwardCompatibility) {
        this.dateBackwardCompatibility = dateBackwardCompatibility;
    }
}


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

import java.io.UnsupportedEncodingException;
import java.util.Date;

import com.microsoft.azure.storage.Constants;
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;

/**
 * Represents a message in the Microsoft Azure Queue service.
 */
public class CloudQueueMessage {

    /**
     * Holds the dequeue count.
     */
    private int dequeueCount;

    /**
     * Holds the time that the message expires.
     */
    private Date expirationTime;

    /**
     * Holds the message ID.
     */
    private String id;

    /**
     * Holds the time that the message was added to the queue.
     */
    private Date insertionTime;

    /**
     * Holds the message content.
     */
    protected String messageContent;

    /**
     * Holds the message type.
     */
    private QueueMessageType messageType;

    /**
     * Holds the time that the message will next be visible.
     */
    private Date nextVisibleTime;

    /**
     * Holds the message's pop receipt.
     */
    private String popReceipt;

    /**
     * Initializes a new instance of the CloudQueueMessage class (used internally).
     */
    protected CloudQueueMessage() {
        // no op
    }

    /**
     * Initializes a new instance of the CloudQueueMessage class with a byte
     * array containing the message.
     *
     * @param content
     *        A byte array which contains the message.
     */
    public CloudQueueMessage(final byte[] content) {
        this.setMessageContent(content);
    }

    /**
     * Initializes a new instance of the CloudQueueMessage class with a String
     * containing the message.
     *
     * @param content
     *        A String which contains the message.
     */
    public CloudQueueMessage(final String content) {
        this.setMessageContent(content);
    }

    /**
     * Gets the dequeue count.
     *
     * @return An int which contains the dequeue count.
     */
    public final int getDequeueCount() {
        return this.dequeueCount;
    }

    /**
     * Gets the time that the message expires.
     *
     * @return A java.util.Date object which represents the time that the message expires.
     */
    public final Date getExpirationTime() {
        return this.expirationTime;
    }

    /**
     * Gets the message ID.
     *
     * @return A String which contains the message ID.
     */
    public final String getId() {
        return this.id;
    }

    /**
     * Gets the time the message was added to the queue.
     *
     * @return A java.util.Date which represents the time the message was added to the queue.
     */
    public final Date getInsertionTime() {
        return this.insertionTime;
    }

    /**
     * Gets the content of the message as a byte array.
     *
     * @return A byte array which contains the content of the message.
     *
     * @throws StorageException
     *         If a storage service error occurred.
     */
    public final byte[] getMessageContentAsByte() throws StorageException {
        if (Utility.isNullOrEmpty(this.messageContent)) {
            return new byte[0];
        }

        if (this.messageType == QueueMessageType.RAW_STRING) {
            try {
                return this.messageContent.getBytes(Constants.UTF8_CHARSET);
            }
            catch (final UnsupportedEncodingException e) {
                throw Utility.generateNewUnexpectedStorageException(e);
            }
        }
        else {
            return Base64.decode(this.messageContent);
        }
    }

    /**
     * Gets the content of the message as a string.
     *
     * @return A String which contains the content of the message.
     *
     * @throws StorageException
     *         If a storage service error occurred.
     */
    public final String getMessageContentAsString() throws StorageException {
        if (this.messageType == QueueMessageType.RAW_STRING) {
            return this.messageContent;
        }
        else {
            if (Utility.isNullOrEmpty(this.messageContent)) {
                return null;
            }

            try {
                return new String(Base64.decode(this.messageContent), Constants.UTF8_CHARSET);
            }
            catch (final UnsupportedEncodingException e) {
                throw Utility.generateNewUnexpectedStorageException(e);
            }
        }
    }

    /**
     * Gets the content of the message for transfer (internal use only).
     *
     * @return A String which contains the content of the message.
     *
     * @throws StorageException
     *         If a storage service error occurred.
     */
    protected final String getMessageContentForTransfer(final boolean shouldEncodeMessage) throws StorageException {
        String result = null;
        if (this.messageType == QueueMessageType.RAW_STRING && shouldEncodeMessage) {
            result = Base64.encode(this.getMessageContentAsByte());
        }
        else {
            result = this.messageContent;
        }

        if (result != null && result.length() > QueueConstants.MAX_MESSAGE_SIZE) {
            throw new IllegalArgumentException(
                    String.format(SR.INVALID_MESSAGE_LENGTH, QueueConstants.MAX_MESSAGE_SIZE));
        }

        return result;
    }

    /**
     * Gets the message ID.
     *
     * @return A String which contains the message ID.
     */
    public final String getMessageId() {
        return this.id;
    }

    /**
     * Gets the message type.
     *
     * @return A QueueMessageType value which represents the message type.
     */
    protected final QueueMessageType getMessageType() {
        return this.messageType;
    }

    /**
     * Gets the time that the message will next be visible.
     *
     * @return A java.util.Date with the time that the message will
     *         next be visible.
     */
    public final Date getNextVisibleTime() {
        return this.nextVisibleTime;
    }

    /**
     * Gets the message's pop receipt.
     *
     * @return A String which contains the message's pop receipt.
     */
    public final String getPopReceipt() {
        return this.popReceipt;
    }

    /**
     * Sets the dequeue count.
     *
     * @param dequeueCount
     *        An int which contains the dequeue count.
     */
    protected final void setDequeueCount(int dequeueCount) {
        this.dequeueCount = dequeueCount;
    }

    /**
     * Sets the expiration time for the message.
     *
     * @param expirationTime
     *        The java.util.Date which represents the expiration time to set on the message.
     */
    protected final void setExpirationTime(final Date expirationTime) {
        this.expirationTime = expirationTime;
    }

    /**
     * Sets the java.util.Date representing the time the message was added to the queue.
     *
     * @param insertiontTime
     *        The java.util.Date representing the time the message was added to the queue.
     */
    protected final void setInsertionTime(Date insertionTime) {
        this.insertionTime = insertionTime;
    }

    /**
     * Sets the content of the message as a byte array.
     *
     * @param content
     *        A byte array which contains the content of the message.
     */
    public final void setMessageContent(final byte[] content) {
        Utility.assertNotNull("content", content);

        this.messageContent = Base64.encode(content);
        this.messageType = QueueMessageType.BASE_64_ENCODED;
    }

    /**
     * Sets the content of the message as a String.
     *
     * @param content
     *        A String which contains the content of the message.
     */
    public final void setMessageContent(final String content) {
        this.messageContent = content;
        this.messageType = QueueMessageType.RAW_STRING;
    }

    /**
     * Sets the message ID.
     *
     * @param id
     *        A String which represents the message ID.
     */
    protected final void setMessageId(String id) {
        this.id = id;
    }

    /**
     * Sets the message's type
     *
     * @param messageType
     *        A QueueMessageType value which represents the message type.
     */
    protected final void setMessageType(final QueueMessageType messageType) {
        this.messageType = messageType;
    }

    /**
     * Sets the time for the message to become visible in the queue.
     *
     * @param nextVisibleTime
     *        A java.util.Date with the time that the message will
     *        next be visible.
     */
    protected final void setNextVisibleTime(final Date nextVisibleTime) {
        this.nextVisibleTime = nextVisibleTime;
    }

    /**
     * Sets the message's pop receipt.
     *
     * @param popReceipt
     *        A String which contains the message's pop receipt.
     */
    protected final void setPopReceipt(final String popReceipt) {
        this.popReceipt = popReceipt;
    }
}


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

import java.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 SharedAccessQueuePolicy extends SharedAccessPolicy {
    /**
     * The permissions for a shared access signature associated with this shared access policy.
     */
    private EnumSet<SharedAccessQueuePermissions> permissions;

    /**
     * Gets the permissions for a shared access signature associated with this shared access policy.
     *
     * @return A java.util.EnumSet object that contains {@link SharedAccessQueuePermissions} values that
     *         represents the set of shared access permissions.
     */
    public EnumSet<SharedAccessQueuePermissions> 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 SharedAccessQueuePermissions} values, to set for the shared access signature.
     */
    public void setPermissions(final EnumSet<SharedAccessQueuePermissions> permissions) {
        this.permissions = permissions;
    }

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

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

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

        if (this.permissions.contains(SharedAccessQueuePermissions.ADD)) {
            builder.append("a");
        }

        if (this.permissions.contains(SharedAccessQueuePermissions.UPDATE)) {
            builder.append("u");
        }

        if (this.permissions.contains(SharedAccessQueuePermissions.PROCESSMESSAGES)) {
            builder.append("p");
        }

        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 that they must be lower case, and the order that they are specified
     *            must be in the order of "raup".
     *

     *
d: Read access.

     *
l: Add access.

     *
r: Update access.

     *
w: ProcessMessages access.

     *

     */
    @Override
    public void setPermissionsFromString(final String value) {
        final char[] chars = value.toCharArray();
        final EnumSet<SharedAccessQueuePermissions> retSet = EnumSet.noneOf(SharedAccessQueuePermissions.class);

        for (final char c : chars) {
            switch (c) {
                case 'r':
                    retSet.add(SharedAccessQueuePermissions.READ);
                    break;
                case 'a':
                    retSet.add(SharedAccessQueuePermissions.ADD);
                    break;
                case 'u':
                    retSet.add(SharedAccessQueuePermissions.UPDATE);
                    break;
                case 'p':
                    retSet.add(SharedAccessQueuePermissions.PROCESSMESSAGES);
                    break;
                default:
                    throw new IllegalArgumentException("value");
            }
        }

        this.permissions = retSet;
    }
}


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

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.Stack;

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

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

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.ListResponse;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * RESERVED FOR INTERNAL USE. A class used to deserialize a list of files and directories.
 */
final class FileListHandler extends DefaultHandler {

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

    private final ListResponse<ListFileItem> response = new ListResponse<ListFileItem>();

    private final CloudFileDirectory directory;

    private FileDirectoryProperties directoryProperties;
    private FileProperties fileProperties;
    private String name;

    private FileListHandler(CloudFileDirectory directory) {
        this.directory = directory;
    }

    /**
     * Parse and return the response.
     *
     * @param stream
     * @param directory
     * @return
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    public static ListResponse<ListFileItem> getFileAndDirectoryList(final InputStream stream,
            final CloudFileDirectory directory) throws ParserConfigurationException, SAXException, IOException {
        SAXParser saxParser = Utility.getSAXParser();
        FileListHandler handler = new FileListHandler(directory);
        saxParser.parse(stream, handler);

        return handler.response;
    }

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

        if (FileConstants.FILE_ELEMENT.equals(localName)) {
            this.name = Constants.EMPTY_STRING;
            this.fileProperties = new FileProperties();
        }

        if (FileConstants.DIRECTORY_ELEMENT.equals(localName)) {
            this.name = Constants.EMPTY_STRING;
            this.directoryProperties = new FileDirectoryProperties();
        }
    }

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

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

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

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

        if (FileConstants.FILE_ELEMENT.equals(currentNode)) {
            CloudFile retFile = null;
            try {
                retFile = this.directory.getFileReference(this.name);
            }
            catch (URISyntaxException e) {
                throw new SAXException(e);
            }
            catch (StorageException e) {
                throw new SAXException(e);
            }
            retFile.setProperties(this.fileProperties);
            this.response.getResults().add(retFile);
        }
        else if (FileConstants.DIRECTORY_ELEMENT.equals(currentNode)) {
            CloudFileDirectory retDirectory = null;
            try {
                retDirectory = this.directory.getDirectoryReference(this.name);
            }
            catch (URISyntaxException e) {
                throw new SAXException(e);
            }
            catch (StorageException e) {
                throw new SAXException(e);
            }
            retDirectory.setProperties(this.directoryProperties);
            this.response.getResults().add(retDirectory);
        }
        else if (ListResponse.ENUMERATION_RESULTS.equals(parentNode)) {
            if (Constants.PREFIX_ELEMENT.equals(currentNode)) {
                this.response.setPrefix(value);
            }
            else if (Constants.MARKER_ELEMENT.equals(currentNode)) {
                this.response.setMarker(value);
            }
            else if (Constants.NEXT_MARKER_ELEMENT.equals(currentNode)) {
                this.response.setNextMarker(value);
            }
            else if (Constants.MAX_RESULTS_ELEMENT.equals(currentNode)) {
                this.response.setMaxResults(Integer.parseInt(value));
            }
        }
        else if (FileConstants.FILE_ELEMENT.equals(parentNode) || FileConstants.DIRECTORY_ELEMENT.equals(parentNode)) {
            if (Constants.NAME_ELEMENT.equals(currentNode)) {
                this.name = value;
            }
        }
        else if (Constants.PROPERTIES.equals(parentNode)) {
            try {
                this.setProperties(currentNode, value);
            }
            catch (ParseException e) {
                throw new SAXException(e);
            }
        }

        this.bld = new StringBuilder();
    }

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

    private void setProperties(String currentNode, String value) throws ParseException {
        // Called in both the file and the directory case.
        if (Constants.LAST_MODIFIED_ELEMENT.equals(currentNode)) {
            this.directoryProperties.setLastModified(Utility.parseRFC1123DateFromStringInGMT(value));
        }
        else if (Constants.ETAG_ELEMENT.equals(currentNode)) {
            this.directoryProperties.setEtag(Utility.formatETag(value));
        }
        else if (Constants.HeaderConstants.CONTENT_LENGTH.equals(currentNode)) {
            this.fileProperties.setLength(Long.parseLong(value));
        }
    }
}


/**
 * 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.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.BaseRequest;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;

/**
 * Reserved for internal use. A class used to generate requests for Table objects.
 */
final class TableRequest {
    /**
     * Reserved for internal use. Adds continuation token values to the specified query builder, if set.
     *
     * @param builder
     *            The {@link UriQueryBuilder} object to apply the continuation token properties to.
     * @param continuationToken
     *            The {@link ResultContinuation} object containing the continuation token values to apply to the query
     *            builder. Specify null if no continuation token values are set.
     *
     * @throws StorageException
     *             if an error occurs in accessing the query builder or continuation token.
     */
    private static void applyContinuationToQueryBuilder(final UriQueryBuilder builder,
            final ResultContinuation continuationToken) throws StorageException {
        if (continuationToken != null) {
            if (continuationToken.getNextPartitionKey() != null) {
                builder.add(TableConstants.TABLE_SERVICE_NEXT_PARTITION_KEY, continuationToken.getNextPartitionKey());
            }

            if (continuationToken.getNextRowKey() != null) {
                builder.add(TableConstants.TABLE_SERVICE_NEXT_ROW_KEY, continuationToken.getNextRowKey());
            }

            if (continuationToken.getNextTableName() != null) {
                builder.add(TableConstants.TABLE_SERVICE_NEXT_TABLE_NAME, continuationToken.getNextTableName());
            }
        }
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform a table batch operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. This parameter is unused.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param batchID
     *            The String containing the batch identifier.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection batch(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String batchID)
            throws IOException, URISyntaxException, StorageException {
        final URI queryUri = PathUtility.appendPathToSingleUri(rootUri, "$batch");

        final HttpURLConnection retConnection = BaseRequest.createURLConnection(queryUri, tableOptions, queryBuilder,
                opContext);

        setAcceptHeaderForHttpWebRequest(retConnection, tableOptions.getTablePayloadFormat());
        retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE,
                String.format(TableConstants.HeaderConstants.MULTIPART_MIXED_FORMAT, batchID));

        retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION,
                TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE);

        retConnection.setRequestMethod("POST");
        retConnection.setDoOutput(true);
        return retConnection;
    }

    /**
     * Reserved for internal use. Constructs the core HttpURLConnection to perform an operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param queryBuilder
     *            The UriQueryBuilder for the request.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            The identity of the entity, to pass in the Service Managment REST operation URI as
     *            tableName(identity). If null, only the tableName
     *            value will be passed.
     * @param requestMethod
     *            The HTTP request method to set.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. This parameter is unused.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    private static HttpURLConnection coreCreate(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String eTag, final String identity, final String requestMethod) throws IOException,
            URISyntaxException, StorageException {

        URI queryUri = null;

        // Do point query / delete etc.
        if (!Utility.isNullOrEmpty(identity)) {
            queryUri = PathUtility.appendPathToSingleUri(rootUri, tableName.concat(String.format("(%s)", identity)));
        }
        else {
            queryUri = PathUtility.appendPathToSingleUri(rootUri, tableName);
        }

        final HttpURLConnection retConnection = BaseRequest.createURLConnection(queryUri, tableOptions, queryBuilder,
                opContext);

        setAcceptHeaderForHttpWebRequest(retConnection, tableOptions.getTablePayloadFormat());

        retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE,
                TableConstants.HeaderConstants.JSON_CONTENT_TYPE);

        retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION,
                TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE);

        if (!Utility.isNullOrEmpty(eTag)) {
            retConnection.setRequestProperty(Constants.HeaderConstants.IF_MATCH, eTag);
        }

        retConnection.setRequestMethod(requestMethod);
        return retConnection;
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform a delete operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            The identity of the entity. The resulting request will be formatted as /tableName(identity) if not
     *            null or empty.
     * @param eTag
     *            The etag of the entity.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection delete(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String identity, final String eTag) throws IOException, URISyntaxException, StorageException {

        return coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName, eTag, identity, "DELETE");
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform an insert operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            The identity of the entity. The resulting request will be formatted as /tableName(identity) if not
     *            null or empty.
     * @param eTag
     *            The etag of the entity, can be null for straight inserts.
     * @param updateType
     *            The {@link TableUpdateType} type of update to be performed. Specify null for straight
     *            inserts.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection insert(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String identity, final String eTag, final boolean echoContent, final TableUpdateType updateType)
            throws IOException, URISyntaxException, StorageException {
        HttpURLConnection retConnection = null;

        if (updateType == null) {
            retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName, eTag,
                    null/* identity */, "POST");
            retConnection.setRequestProperty(TableConstants.HeaderConstants.PREFER,
                    echoContent ? TableConstants.HeaderConstants.RETURN_CONTENT
                            : TableConstants.HeaderConstants.RETURN_NO_CONTENT);
        }
        else if (updateType == TableUpdateType.MERGE) {
            retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName, null/* ETAG */,
                    identity, "POST");

            retConnection.setRequestProperty(TableConstants.HeaderConstants.X_HTTP_METHOD, "MERGE");
        }
        else if (updateType == TableUpdateType.REPLACE) {
            retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName, null/* ETAG */,
                    identity, "PUT");
        }

        retConnection.setDoOutput(true);

        return retConnection;
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform a merge operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            The identity of the entity. The resulting request will be formatted as /tableName(identity) if not
     *            null or empty.
     * @param eTag
     *            The etag of the entity.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection merge(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String identity, final String eTag) throws IOException, URISyntaxException, StorageException {
        final HttpURLConnection retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName,
                eTag, identity, "POST");
        retConnection.setRequestProperty(TableConstants.HeaderConstants.X_HTTP_METHOD, "MERGE");
        retConnection.setDoOutput(true);
        return retConnection;
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform a single entity query operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            The identity of the entity. The resulting request will be formatted as /tableName(identity) if not
     *            null or empty.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection query(final URI rootUri, final TableRequestOptions tableOptions,
            UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String identity, final ResultContinuation continuationToken) throws IOException, URISyntaxException,
            StorageException {
        if (queryBuilder == null) {
            queryBuilder = new UriQueryBuilder();
        }

        applyContinuationToQueryBuilder(queryBuilder, continuationToken);
        final HttpURLConnection retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName,
                null, identity, "GET");

        return retConnection;
    }

    /**
     * Reserved for internal use. Constructs an HttpURLConnection to perform an update operation.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param queryBuilder
     *            The {@link UriQueryBuilder} for the operation.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     * @param tableName
     *            The name of the table.
     * @param identity
     *            A String representing the identity of the entity. The resulting request will be formatted
     *            using /tableName(identity) if identity is not >code>null or empty.
     * @param eTag
     *            The etag of the entity.
     * @return
     *         An HttpURLConnection to use to perform the operation.
     *
     * @throws IOException
     *             if there is an error opening the connection.
     * @throws URISyntaxException
     *             if the resource URI is invalid.
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    public static HttpURLConnection update(final URI rootUri, final TableRequestOptions tableOptions,
            final UriQueryBuilder queryBuilder, final OperationContext opContext, final String tableName,
            final String identity, final String eTag) throws IOException, URISyntaxException, StorageException {
        final HttpURLConnection retConnection = coreCreate(rootUri, tableOptions, queryBuilder, opContext, tableName,
                eTag, identity, "PUT");

        retConnection.setDoOutput(true);
        return retConnection;
    }

    /**
     * Sets the ACL for the table. Sign with length of aclBytes.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     * */
    public static HttpURLConnection setAcl(final URI rootUri, final TableRequestOptions options,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {

        UriQueryBuilder queryBuilder = new UriQueryBuilder();
        queryBuilder.add(Constants.QueryConstants.COMPONENT, "acl");

        final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, options, queryBuilder,
                opContext);
        retConnection.setRequestMethod("PUT");
        retConnection.setDoOutput(true);

        return retConnection;
    }

    /**
     * Constructs a web request to return the ACL for this table. Sign with no length specified.
     *
     * @param rootUri
     *            A java.net.URI containing an absolute URI to the resource.
     * @param tableOptions
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return a HttpURLConnection configured for the operation.
     * @throws StorageException
     */
    public static HttpURLConnection getAcl(final URI rootUri, final TableRequestOptions options,
            final OperationContext opContext) throws IOException, URISyntaxException, StorageException {

        UriQueryBuilder queryBuilder = new UriQueryBuilder();
        queryBuilder.add(Constants.QueryConstants.COMPONENT, "acl");

        final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, options, queryBuilder,
                opContext);
        retConnection.setRequestMethod("GET");

        return retConnection;
    }

    private static void setAcceptHeaderForHttpWebRequest(HttpURLConnection retConnection,
            TablePayloadFormat payloadFormat) {
        if (payloadFormat == TablePayloadFormat.JsonFullMetadata) {
            retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT,
                    TableConstants.HeaderConstants.JSON_FULL_METADATA_ACCEPT_TYPE);
        }
        else if (payloadFormat == TablePayloadFormat.Json) {
            retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT,
                    TableConstants.HeaderConstants.JSON_ACCEPT_TYPE);
        }
        else if (payloadFormat == TablePayloadFormat.JsonNoMetadata) {
            retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT,
                    TableConstants.HeaderConstants.JSON_NO_METADATA_ACCEPT_TYPE);
        }
    }

    /**
     * Private Default Constructor.
     */
    private TableRequest() {
        // No op
    }
}


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

import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;

import org.xmlpull.v1.XmlSerializer;

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

/**
 * RESERVED FOR INTERNAL USE. A class used to serialize ServiceProperties to a byte array.
 */
final class ServicePropertiesSerializer {

    /**
     * Writes the contents of the ServiceProperties to the stream in xml format.
     *
     * @return a byte array of the content to write to the stream.
     * @throws IOException
     *             if there is an error writing the service properties.
     * @throws IllegalStateException
     *             if there is an error writing the service properties.
     * @throws IllegalArgumentException
     *             if there is an error writing the service properties.
     */
    public static byte[] serializeToByteArray(final ServiceProperties properties) throws IllegalArgumentException,
            IllegalStateException, IOException {
        final StringWriter outWriter = new StringWriter();
        final XmlSerializer xmlw = Utility.getXmlSerializer(outWriter);

        // default is UTF8
        xmlw.startDocument(Constants.UTF8_CHARSET, true);
        xmlw.startTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.STORAGE_SERVICE_PROPERTIES_ELEMENT);

        if (properties.getLogging() != null) {
            // Logging Properties
            writeLoggingProperties(xmlw, properties.getLogging());
        }

        if (properties.getHourMetrics() != null) {
            // Hour Metrics
            writeMetricsProperties(xmlw, properties.getHourMetrics(), Constants.AnalyticsConstants.HOUR_METRICS_ELEMENT);
        }

        if (properties.getMinuteMetrics() != null) {
            // Minute Metrics
            writeMetricsProperties(xmlw, properties.getMinuteMetrics(),
                    Constants.AnalyticsConstants.MINUTE_METRICS_ELEMENT);
        }

        if (properties.getCors() != null) {
            // CORS Properties
            writeCorsProperties(xmlw, properties.getCors());
        }

        // Default Service Version
        if (properties.getDefaultServiceVersion() != null) {
            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.DEFAULT_SERVICE_VERSION,
                    properties.getDefaultServiceVersion());
        }

        // end StorageServiceProperties
        xmlw.endTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.STORAGE_SERVICE_PROPERTIES_ELEMENT);

        // end doc
        xmlw.endDocument();

        return outWriter.toString().getBytes(Constants.UTF8_CHARSET);
    }

    /**
     * Writes the retention policy to the XMLStreamWriter.
     *
     * @param xmlw
     *            the XMLStreamWriter to write to.
     * @param val
     *            the nullable Integer indicating if the retention policy is enabled, and how long
     * @throws IOException
     *             if there is an error writing the service properties.
     * @throws IllegalStateException
     *             if there is an error writing the service properties.
     * @throws IllegalArgumentException
     *             if there is an error writing the service properties.
     */
    private static void writeRetentionPolicy(final XmlSerializer xmlw, final Integer val)
            throws IllegalArgumentException, IllegalStateException, IOException {
        xmlw.startTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.RETENTION_POLICY_ELEMENT);

        // Enabled
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.ENABLED_ELEMENT, val != null ? Constants.TRUE
                : Constants.FALSE);

        if (val != null) {
            // Days
            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.DAYS_ELEMENT, val.toString());
        }

        // End Retention Policy
        xmlw.endTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.RETENTION_POLICY_ELEMENT);
    }

    /**
     * Writes the given CORS properties to the XMLStreamWriter.
     *
     * @param xmlw
     *            the XMLStreamWriter to write to.
     * @param cors
     *            the CORS Properties to be written.
     * @throws IOException
     *             if there is an error writing the service properties.
     * @throws IllegalStateException
     *             if there is an error writing the service properties.
     * @throws IllegalArgumentException
     *             if there is an error writing the service properties.
     */
    private static void writeCorsProperties(final XmlSerializer xmlw, final CorsProperties cors)
            throws IllegalArgumentException, IllegalStateException, IOException {
        Utility.assertNotNull("CorsRules", cors.getCorsRules());

        // CORS
        xmlw.startTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.CORS_ELEMENT);

        for (CorsRule rule : cors.getCorsRules()) {
            if (rule.getAllowedOrigins().isEmpty() || rule.getAllowedMethods().isEmpty()
                    || rule.getMaxAgeInSeconds() < 0) {
                throw new IllegalArgumentException(SR.INVALID_CORS_RULE);
            }

            xmlw.startTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.CORS_RULE_ELEMENT);

            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.ALLOWED_ORIGINS_ELEMENT,
                    joinToString(rule.getAllowedOrigins(), ","));

            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.ALLOWED_METHODS_ELEMENT,
                    joinToString(rule.getAllowedMethods(), ","));

            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.EXPOSED_HEADERS_ELEMENT,
                    joinToString(rule.getExposedHeaders(), ","));

            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.ALLOWED_HEADERS_ELEMENT,
                    joinToString(rule.getAllowedHeaders(), ","));

            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.MAX_AGE_IN_SECONDS_ELEMENT,
                    Integer.toString(rule.getMaxAgeInSeconds()));

            xmlw.endTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.CORS_RULE_ELEMENT);
        }

        // end CORS
        xmlw.endTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.CORS_ELEMENT);
    }

    /**
     * Writes the given metrics properties to the XMLStreamWriter.
     *
     * @param xmlw
     *            the XMLStreamWriter to write to.
     * @param metrics
     *            the metrics properties to be written.
     * @param metricsName
     *            the type of metrics properties to be written (Hour or Minute)
     * @throws IOException
     *             if there is an error writing the service properties.
     * @throws IllegalStateException
     *             if there is an error writing the service properties.
     * @throws IllegalArgumentException
     *             if there is an error writing the service properties.
     */
    private static void writeMetricsProperties(final XmlSerializer xmlw, final MetricsProperties metrics,
            final String metricsName) throws IllegalArgumentException, IllegalStateException, IOException {
        Utility.assertNotNull("metrics.Configuration", metrics.getMetricsLevel());

        // Metrics
        xmlw.startTag(Constants.EMPTY_STRING, metricsName);

        // Version
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.VERSION_ELEMENT, metrics.getVersion());

        // Enabled
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.ENABLED_ELEMENT,
                metrics.getMetricsLevel() != MetricsLevel.DISABLED ? Constants.TRUE : Constants.FALSE);

        if (metrics.getMetricsLevel() != MetricsLevel.DISABLED) {
            // Include APIs
            Utility.serializeElement(xmlw, Constants.AnalyticsConstants.INCLUDE_APIS_ELEMENT,
                    metrics.getMetricsLevel() == MetricsLevel.SERVICE_AND_API ? Constants.TRUE : Constants.FALSE);
        }

        // Retention Policy
        writeRetentionPolicy(xmlw, metrics.getRetentionIntervalInDays());

        // end Metrics
        xmlw.endTag(Constants.EMPTY_STRING, metricsName);
    }

    /**
     * Writes the given logging properties to the XMLStreamWriter.
     *
     * @param xmlw
     *            the XMLStreamWriter to write to.
     * @param cors
     *            the logging properties to be written.
     * @throws IOException
     *             if there is an error writing the service properties.
     * @throws IllegalStateException
     *             if there is an error writing the service properties.
     * @throws IllegalArgumentException
     *             if there is an error writing the service properties.
     */
    private static void writeLoggingProperties(final XmlSerializer xmlw, final LoggingProperties logging)
            throws IllegalArgumentException, IllegalStateException, IOException {
        Utility.assertNotNull("logging.LogOperationTypes", logging.getLogOperationTypes());

        // Logging
        xmlw.startTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.LOGGING_ELEMENT);

        // Version
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.VERSION_ELEMENT, logging.getVersion());

        // Delete
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.DELETE_ELEMENT, logging.getLogOperationTypes()
                .contains(LoggingOperations.DELETE) ? Constants.TRUE : Constants.FALSE);

        // Read
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.READ_ELEMENT, logging.getLogOperationTypes()
                .contains(LoggingOperations.READ) ? Constants.TRUE : Constants.FALSE);

        // Write
        Utility.serializeElement(xmlw, Constants.AnalyticsConstants.WRITE_ELEMENT, logging.getLogOperationTypes()
                .contains(LoggingOperations.WRITE) ? Constants.TRUE : Constants.FALSE);

        // Retention Policy
        writeRetentionPolicy(xmlw, logging.getRetentionIntervalInDays());

        // end Logging
        xmlw.endTag(Constants.EMPTY_STRING, Constants.AnalyticsConstants.LOGGING_ELEMENT);
    }

    /**
     * Concatenate an Iterable<?> set of items with a delimiter between each
     *
     * @param iterable
     *            the set of items to concatenate
     * @param delimiter
     *            the character to put between each item
     * @return the concatenated string
     */
    private static String joinToString(Iterable<?> iterable, String delimiter) {
        StringBuilder builder = new StringBuilder();
        Iterator<?> iter = iterable.iterator();
        while (iter.hasNext()) {
            builder.append(iter.next());
            if (iter.hasNext()) {
                builder.append(delimiter);
            }
        }
        return builder.toString();
    }
}