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

import java.net.Inet4Address;

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

/**
 * A continuous range of IP addresses.
 */
public final class IPRange {
    private String ipMin;
    private String ipMax;

    /**
     * Creates an IP Range using the specified single IP address. The IP address must be IPv4.
     *
     * @param ip
     *            the single IP address
     */
    public IPRange(String ip) {
        Utility.assertNotNull("ip", ip);
        IPRange.validateIPAddress(ip);

        this.ipMin = ip;
        this.ipMax = ip;
    }

    /**
     * Creates an IP Range using the specified minimum and maximum IP addresses. The IP addresses must be IPv4.
     *
     * @param mininimumIP
     *            the minimum IP address of the range
     * @param maximumIP
     *            the maximum IP address of the range
     */
    public IPRange(String mininimumIP, String maximumIP) {
        Utility.assertNotNull("mininimumIP", mininimumIP);
        Utility.assertNotNull("maximumIP", maximumIP);

        IPRange.validateIPAddress(mininimumIP);
        IPRange.validateIPAddress(maximumIP);

        this.ipMin = mininimumIP;
        this.ipMax = maximumIP;
    }

    /**
     * The minimum IP address for the range, inclusive.
     * Will match {@link #getIpMax()} if this IPRange represents a single IP address.
     *
     * @return The minimum IP address
     */
    public String getIpMin() {
        return this.ipMin;
    }

    /**
     * The maximum IP address for the range, inclusive.
     * Will match {@link #getIpMin()} if this IPRange represents a single IP address.
     *
     * @return The maximum IP address
     */
    public String getIpMax() {
        return this.ipMax;
    }

    /**
     * Output the single IP address or range of IP addresses.
     *
     * @return the single IP address or range of IP addresses formated as a String
     */
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder(this.ipMin);
        if (!this.ipMin.equals(this.ipMax)) {
            str.append("-");
            str.append(this.ipMax);
        }

        return str.toString();
    }

    /**
     * Validate that the IP address is IPv4.
     *
     * @param ipAddress
     *              the IP address to validate
     */
    private static void validateIPAddress(String ipAddress) {
        try {
            @SuppressWarnings("unused")
            Inet4Address address = (Inet4Address) Inet4Address.getByName(ipAddress);
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(String.format(SR.INVALID_IP_ADDRESS, ipAddress), ex);
        }
    }
}


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

/**
 * RESERVED FOR INTERNAL USE. A class used to deserialize share stats.
 */
final class ShareStatsHandler extends DefaultHandler {
    /**
     * The name of the share usage XML element.
     */
    private final static String USAGE_NAME = "ShareUsage";

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

    private final ShareStats stats = new ShareStats();

    /**
     * Constructs a {@link ShareStats} object from an XML document received from the service.
     *
     * @param inStream
     *            The XMLStreamReader object.
     * @return
     *         A {@link ShareStats} object containing the properties in the XML document.
     *
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws IOException
     */
    public static ShareStats readShareStatsFromStream(final InputStream inStream)
            throws ParserConfigurationException, SAXException, IOException {
        SAXParser saxParser = Utility.getSAXParser();
        ShareStatsHandler handler = new ShareStatsHandler();
        saxParser.parse(inStream, handler);

        return handler.stats;
    }

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

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

        if (USAGE_NAME.equals(currentNode)) {
            this.stats.setUsage(Integer.parseInt(this.builder.toString()));
        }

        this.builder = new StringBuilder();
    }

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


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

import java.net.HttpURLConnection;
import java.util.Date;

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

/**
 * Abstract class that represents a retry policy.
 */
public abstract class RetryPolicy implements RetryPolicyFactory {

    /**
     * Represents the default client backoff interval, in milliseconds.
     */
    public static final int DEFAULT_CLIENT_BACKOFF = 1000 * 30;

    /**
     * Represents the default client retry count.
     */
    public static final int DEFAULT_CLIENT_RETRY_COUNT = 3;

    /**
     * Represents the default maximum backoff interval, in milliseconds.
     */
    public static final int DEFAULT_MAX_BACKOFF = 1000 * 90;

    /**
     * Represents the default minimum backoff interval, in milliseconds.
     */
    public static final int DEFAULT_MIN_BACKOFF = 1000 * 3;

    /**
     * Represents the realized backoff interval, in milliseconds.
     */
    protected int deltaBackoffIntervalInMs;

    /**
     * Represents the maximum retries that the retry policy should attempt.
     */
    protected int maximumAttempts;

    /**
     * Represents the time of the last request attempt to the primary location.
     */
    protected Date lastPrimaryAttempt = null;

    /**
     * Represents the time of the last request attempt to the secondary location.
     */
    protected Date lastSecondaryAttempt = null;

    /**
     * Creates an instance of the RetryPolicy class.
     */
    public RetryPolicy() {
        // Empty Default Ctor
    }

    /**
     * Creates an instance of the RetryPolicy class using the specified delta backoff and maximum retry
     * attempts.
     *
     * @param deltaBackoff
     *            The backoff interval, in milliseconds, between retries.
     * @param maxAttempts
     *            The maximum number of retry attempts.
     */
    public RetryPolicy(final int deltaBackoff, final int maxAttempts) {
        this.deltaBackoffIntervalInMs = deltaBackoff;
        this.maximumAttempts = maxAttempts;
    }

    /**
     * Determines whether the operation should be retried and specifies the interval until the next retry.
     *
     * @param retryContext
     *            A {@link RetryContext} object that indicates the number of retries, last request's results, whether
     *            the next retry should happen in the primary or secondary location, and specifies the location mode.
     * @param operationContext
     *            An {@link OperationContext} object for tracking the current operation.
     * @return
     *         A {@link RetryInfo} object that indicates whether the next retry will happen in the primary or secondary
     *         location, and specifies the location mode. If null, the operation will not be retried.
     */
    public abstract RetryInfo evaluate(RetryContext retryContext, OperationContext operationContext);

    /**
     * Determines the time of the last attempt to a storage location and returns a boolean that specifies
     * if a request that was sent to the secondary location failed with 404.
     *
     * @param retryContext
     *            A {@link RetryContext} object that indicates the number of retries, last request's results, whether
     *            the next retry should happen in the primary or secondary location, and specifies the location mode.
     * @return
     *         true if a request sent to the secondary location fails with 404 (Not Found).
     *         false otherwise.
     */
    protected boolean evaluateLastAttemptAndSecondaryNotFound(RetryContext retryContext) {
        Utility.assertNotNull("retryContext", retryContext);

        // Retry interval of a request to a location must take the time spent sending requests
        // to other locations into account. For example, assume a request was sent to the primary
        // location first, then to the secondary, and then to the primary again. If it
        // was supposed to wait 10 seconds between requests to the primary and the request to
        // the secondary took 3 seconds in total, retry interval should only be 7 seconds. This is because,
        // in total, the requests will be 10 seconds apart from the primary locations' point of view.
        // For this calculation, current instance of the retry policy stores the time of the last
        // request to a specific location.
        if (retryContext.getLastRequestResult().getTargetLocation() == StorageLocation.PRIMARY) {
            this.lastPrimaryAttempt = retryContext.getLastRequestResult().getStopDate();
        }
        else {
            this.lastSecondaryAttempt = retryContext.getLastRequestResult().getStopDate();
        }

        // If a request sent to the secondary location fails with 404 (Not Found), it is possible
        // that the the asynchronous geo-replication for the resource has not completed. So, in case of 404 only in the secondary
        // location, the failure should still be retried.
        return (retryContext.getLastRequestResult().getTargetLocation() == StorageLocation.SECONDARY)
                && (retryContext.getLastRequestResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);
    }

    /**
     * Determines the {@link RetryInfo} object that indicates whether the next retry will happen in the primary or
     * secondary location, and specifies the location mode.
     *
     * @param retryContext
     *            A {@link RetryContext} object that indicates the number of retries, last request's results, whether
     *            the next retry should happen in the primary or secondary location, and specifies the location mode.
     *
     * @param secondaryNotFound
     *            A boolean representing whether a request sent to the secondary location failed with 404
     *            (Not Found)
     * @param retryInterval
     *            Backoff Interval.
     * @return
     *         A reference to the {@link RetryInfo} object that indicates whether the next retry will happen in the
     *         primary or secondary location, and specifies the location mode.
     */
    protected RetryInfo evaluateRetryInfo(final RetryContext retryContext, final boolean secondaryNotFound,
            final long retryInterval) {
        RetryInfo retryInfo = new RetryInfo(retryContext);

        // Moreover, in case of 404 when trying the secondary location, instead of retrying on the
        // secondary, further requests should be sent only to the primary location, as it most
        // probably has a higher chance of succeeding there.
        if (secondaryNotFound && (retryContext.getLocationMode() != LocationMode.SECONDARY_ONLY)) {
            retryInfo.setUpdatedLocationMode(LocationMode.PRIMARY_ONLY);
            retryInfo.setTargetLocation(StorageLocation.PRIMARY);
        }

        // Now is the time to calculate the exact retry interval. ShouldRetry call above already
        // returned back how long two requests to the same location should be apart from each other.
        // However, for the reasons explained above, the time spent between the last attempt to
        // the target location and current time must be subtracted from the total retry interval
        // that ShouldRetry returned.
        Date lastAttemptTime = retryInfo.getTargetLocation() == StorageLocation.PRIMARY ? this.lastPrimaryAttempt
                : this.lastSecondaryAttempt;
        if (lastAttemptTime != null) {
            long sinceLastAttempt = (new Date().getTime() - lastAttemptTime.getTime() > 0) ? new Date().getTime()
                    - lastAttemptTime.getTime() : 0;
            retryInfo.setRetryInterval((int) (retryInterval - sinceLastAttempt));
        }
        else {
            retryInfo.setRetryInterval(0);
        }

        return retryInfo;
    }
}


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

/**
 * RESERVED FOR INTERNAL USE. Provides Base64 encoding, decoding, and validation functionality.
 */
public final class Base64 {
    /**
     * The Base 64 Characters.
     */
    private static final String BASE_64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * Decoded values, -1 is invalid character, -2 is = pad character.
     */
    private static final byte DECODE_64[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*
                                                                             * 16- 31
                                                                             */
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /*
                                                                             * 32- 47
                                                                             */
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, /*
                                                                             * 48- 63
                                                                             */
            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 64-79 */
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /*
                                                                             * 80- 95
                                                                             */
            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*
                                                                             * 96- 111
                                                                             */
            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 /*
                                                                            * 112- 127
                                                                            */
    };

    /**
     * Decodes a given Base64 string into its corresponding byte array.
     *
     * @param data
     *            the Base64 string, as a String object, to decode
     *
     * @return the corresponding decoded byte array
     * @throws IllegalArgumentException
     *             If the string is not a valid base64 encoded string
     */
    public static byte[] decode(final String data) {
        if (data == null) {
            throw new IllegalArgumentException(SR.STRING_NOT_VALID);
        }

        int byteArrayLength = 3 * data.length() / 4;

        if (data.endsWith("==")) {
            byteArrayLength -= 2;
        }
        else if (data.endsWith("=")) {
            byteArrayLength -= 1;
        }

        final byte[] retArray = new byte[byteArrayLength];
        int byteDex = 0;
        int charDex = 0;

        for (; charDex < data.length(); charDex += 4) {
            // get 4 chars, convert to 3 bytes
            final int char1 = DECODE_64[(byte) data.charAt(charDex)];
            final int char2 = DECODE_64[(byte) data.charAt(charDex + 1)];
            final int char3 = DECODE_64[(byte) data.charAt(charDex + 2)];
            final int char4 = DECODE_64[(byte) data.charAt(charDex + 3)];

            if (char1 < 0 || char2 < 0 || char3 == -1 || char4 == -1) {
                // invalid character(-1), or bad padding (-2)
                throw new IllegalArgumentException(SR.STRING_NOT_VALID);
            }

            int tVal = char1 << 18;
            tVal += char2 << 12;
            tVal += (char3 & 0xff) << 6;
            tVal += char4 & 0xff;

            if (char3 == -2) {
                // two "==" pad chars, check bits 12-24
                tVal &= 0x00FFF000;
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
            }
            else if (char4 == -2) {
                // one pad char "=" , check bits 6-24.
                tVal &= 0x00FFFFC0;
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
                retArray[byteDex++] = (byte) (tVal >> 8 & 0xFF);

            }
            else {
                // No pads take all 3 bytes, bits 0-24
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
                retArray[byteDex++] = (byte) (tVal >> 8 & 0xFF);
                retArray[byteDex++] = (byte) (tVal & 0xFF);
            }
        }
        return retArray;
    }

    /**
     * Decodes a given Base64 string into its corresponding byte array.
     *
     * @param data
     *            the Base64 string, as a String object, to decode
     *
     * @return the corresponding decoded byte array
     * @throws IllegalArgumentException
     *             If the string is not a valid base64 encoded string
     */
    public static Byte[] decodeAsByteObjectArray(final String data) {
        int byteArrayLength = 3 * data.length() / 4;

        if (data.endsWith("==")) {
            byteArrayLength -= 2;
        }
        else if (data.endsWith("=")) {
            byteArrayLength -= 1;
        }

        final Byte[] retArray = new Byte[byteArrayLength];
        int byteDex = 0;
        int charDex = 0;

        for (; charDex < data.length(); charDex += 4) {
            // get 4 chars, convert to 3 bytes
            final int char1 = DECODE_64[(byte) data.charAt(charDex)];
            final int char2 = DECODE_64[(byte) data.charAt(charDex + 1)];
            final int char3 = DECODE_64[(byte) data.charAt(charDex + 2)];
            final int char4 = DECODE_64[(byte) data.charAt(charDex + 3)];

            if (char1 < 0 || char2 < 0 || char3 == -1 || char4 == -1) {
                // invalid character(-1), or bad padding (-2)
                throw new IllegalArgumentException(SR.STRING_NOT_VALID);
            }

            int tVal = char1 << 18;
            tVal += char2 << 12;
            tVal += (char3 & 0xff) << 6;
            tVal += char4 & 0xff;

            if (char3 == -2) {
                // two "==" pad chars, check bits 12-24
                tVal &= 0x00FFF000;
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
            }
            else if (char4 == -2) {
                // one pad char "=" , check bits 6-24.
                tVal &= 0x00FFFFC0;
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
                retArray[byteDex++] = (byte) (tVal >> 8 & 0xFF);

            }
            else {
                // No pads take all 3 bytes, bits 0-24
                retArray[byteDex++] = (byte) (tVal >> 16 & 0xFF);
                retArray[byteDex++] = (byte) (tVal >> 8 & 0xFF);
                retArray[byteDex++] = (byte) (tVal & 0xFF);
            }
        }
        return retArray;
    }

    /**
     * Encodes a byte array as a Base64 string.
     *
     * @param data
     *            the byte array to encode
     * @return the Base64-encoded string, as a String object
     */
    public static String encode(final byte[] data) {
        final StringBuilder builder = new StringBuilder();
        final int dataRemainder = data.length % 3;

        int j = 0;
        int n = 0;
        for (; j < data.length; j += 3) {

            if (j < data.length - dataRemainder) {
                n = ((data[j] & 0xFF) << 16) + ((data[j + 1] & 0xFF) << 8) + (data[j + 2] & 0xFF);
            }
            else {
                if (dataRemainder == 1) {
                    n = (data[j] & 0xFF) << 16;
                }
                else if (dataRemainder == 2) {
                    n = ((data[j] & 0xFF) << 16) + ((data[j + 1] & 0xFF) << 8);
                }
            }

            // Left here for readability
            // byte char1 = (byte) ((n >>> 18) & 0x3F);
            // byte char2 = (byte) ((n >>> 12) & 0x3F);
            // byte char3 = (byte) ((n >>> 6) & 0x3F);
            // byte char4 = (byte) (n & 0x3F);
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 18) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 12) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 6) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) (n & 0x3F)));
        }

        final int bLength = builder.length();

        // append '=' to pad
        if (data.length % 3 == 1) {
            builder.replace(bLength - 2, bLength, "==");
        }
        else if (data.length % 3 == 2) {
            builder.replace(bLength - 1, bLength, "=");
        }

        return builder.toString();
    }

    /**
     * Encodes a byte array as a Base64 string.
     *
     * @param data
     *            the byte array to encode
     * @return the Base64-encoded string, as a String object
     */
    public static String encode(final Byte[] data) {
        final StringBuilder builder = new StringBuilder();
        final int dataRemainder = data.length % 3;

        int j = 0;
        int n = 0;
        for (; j < data.length; j += 3) {

            if (j < data.length - dataRemainder) {
                n = ((data[j] & 0xFF) << 16) + ((data[j + 1] & 0xFF) << 8) + (data[j + 2] & 0xFF);
            }
            else {
                if (dataRemainder == 1) {
                    n = (data[j] & 0xFF) << 16;
                }
                else if (dataRemainder == 2) {
                    n = ((data[j] & 0xFF) << 16) + ((data[j + 1] & 0xFF) << 8);
                }
            }

            // Left here for readability
            // byte char1 = (byte) ((n >>> 18) & 0x3F);
            // byte char2 = (byte) ((n >>> 12) & 0x3F);
            // byte char3 = (byte) ((n >>> 6) & 0x3F);
            // byte char4 = (byte) (n & 0x3F);
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 18) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 12) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) ((n >>> 6) & 0x3F)));
            builder.append(BASE_64_CHARS.charAt((byte) (n & 0x3F)));
        }

        final int bLength = builder.length();

        // append '=' to pad
        if (data.length % 3 == 1) {
            builder.replace(bLength - 2, bLength, "==");
        }
        else if (data.length % 3 == 2) {
            builder.replace(bLength - 1, bLength, "=");
        }

        return builder.toString();
    }

    /**
     * Determines whether the given string contains only Base64 characters.
     *
     * @param data
     *            the string, as a String object, to validate
     * @return true if data is a valid Base64 string, otherwise false
     */
    public static boolean validateIsBase64String(final String data) {

        if (data == null || data.length() % 4 != 0) {
            return false;
        }

        for (int m = 0; m < data.length(); m++) {
            final byte charByte = (byte) data.charAt(m);

            // pad char detected
            if (DECODE_64[charByte] == -2) {
                if (m < data.length() - 2) {
                    return false;
                }
                else if (m == data.length() - 2 && DECODE_64[(byte) data.charAt(m + 1)] != -2) {
                    return false;
                }
            }

            if (charByte < 0 || DECODE_64[charByte] == -1) {
                return false;
            }
        }

        return true;
    }

    /**
     * Private Default Ctor.
     */
    private Base64() {
        // 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.util.regex.Pattern;

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

/**
 * Provides helpers to validate resource names across the Microsoft Azure
 * Storage Services.
 */
public class NameValidator {
	private static final int BLOB_FILE_DIRECTORY_MIN_LENGTH = 1;
	private static final int CONTAINER_SHARE_QUEUE_TABLE_MIN_LENGTH = 3;
	private static final int CONTAINER_SHARE_QUEUE_TABLE_MAX_LENGTH = 63;
	private static final int FILE_DIRECTORY_MAX_LENGTH = 255;
	private static final int BLOB_MAX_LENGTH = 1024;
	private static final Pattern FILE_DIRECTORY_REGEX = Pattern.compile("^[^\"\\/:|<>*?]*/{0,1}");
	private static final Pattern SHARE_CONTAINER_QUEUE_REGEX = Pattern.compile("^[a-z0-9]+(-[a-z0-9]+)*$");
	private static final Pattern TABLE_REGEX = Pattern.compile("^[A-Za-z][A-Za-z0-9]*$");
	private static final Pattern METRICS_TABLE_REGEX = Pattern.compile("^\\$Metrics(HourPrimary|MinutePrimary|HourSecondary|MinuteSecondary)?(Transactions)(Blob|Queue|Table)$");
	private static final String[] RESERVED_FILE_NAMES = { ".", "..", "LPT1",
			"LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
			"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
			"COM9", "PRN", "AUX", "NUL", "CON", "CLOCK$" };

	/**
	 * Checks if a container name is valid.
	 *
	 * @param containerName
	 *            A String representing the container name to validate.
	 */
	public static void validateContainerName(String containerName) {
		if (!("$root".equals(containerName) || "$logs".equals(containerName))) {
			NameValidator.validateShareContainerQueueHelper(containerName, SR.CONTAINER);
		}
	}

	/**
	 * Checks if a queue name is valid.
	 *
	 * @param queueName
	 *            A String representing the queue name to validate.
	 */
	public static void validateQueueName(String queueName) {
		NameValidator.validateShareContainerQueueHelper(queueName, SR.QUEUE);
	}

	/**
	 * Checks if a share name is valid.
	 *
	 * @param shareName
	 *            A String representing the share name to validate.
	 */
	public static void validateShareName(String shareName) {
		NameValidator.validateShareContainerQueueHelper(shareName, SR.SHARE);
	}

	private static void validateShareContainerQueueHelper(String resourceName,
			String resourceType) {
		if (Utility.isNullOrEmptyOrWhitespace(resourceName)) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.RESOURCE_NAME_EMPTY, resourceType));
		}

		if (resourceName.length() < NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MIN_LENGTH || resourceName.length() > NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MAX_LENGTH) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME_LENGTH, resourceType, NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MIN_LENGTH, NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MAX_LENGTH));
		}

		if (!NameValidator.SHARE_CONTAINER_QUEUE_REGEX.matcher(resourceName).matches())
		{
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME, resourceType));
		}
	}

	/**
	 * Checks if a blob name is valid.
	 *
	 * @param blobName
	 *            A String representing the blob name to validate.
	 */
	public static void validateBlobName(String blobName) {
		if (Utility.isNullOrEmptyOrWhitespace(blobName)) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.RESOURCE_NAME_EMPTY, SR.BLOB));
		}

		if (blobName.length() < NameValidator.BLOB_FILE_DIRECTORY_MIN_LENGTH || blobName.length() > NameValidator.BLOB_MAX_LENGTH) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME_LENGTH, SR.BLOB, NameValidator.BLOB_FILE_DIRECTORY_MIN_LENGTH, NameValidator.BLOB_MAX_LENGTH));
		}

		int slashCount =  0;
		for (int i = 0; i < blobName.length(); i++)
		{
			if (blobName.charAt(i) == '/')
			{
				slashCount++;
			}
		}

		if (slashCount >= 254)
		{
			throw new IllegalArgumentException(SR.TOO_MANY_PATH_SEGMENTS);
		}
	}

	/**
	 * Checks if a file name is valid.
	 *
	 * @param fileName
	 *            A String representing the file name to validate.
	 */
	public static void validateFileName(String fileName) {
		NameValidator.ValidateFileDirectoryHelper(fileName, SR.FILE);

		if (fileName.endsWith("/")) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME, SR.FILE));
		}

		for (String s : NameValidator.RESERVED_FILE_NAMES) {
			if (s.equalsIgnoreCase(fileName)) {
				throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_RESERVED_NAME, SR.FILE));
			}
		}
	}

	/**
	 * Checks if a directory name is valid.
	 *
	 * @param directoryName
	 *            A String representing the directory name to validate.
	 */
	public static void validateDirectoryName(String directoryName) {
		NameValidator.ValidateFileDirectoryHelper(directoryName, SR.DIRECTORY);
	}

	private static void ValidateFileDirectoryHelper(String resourceName, String resourceType) {
		if (Utility.isNullOrEmptyOrWhitespace(resourceName)) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.RESOURCE_NAME_EMPTY, resourceType));
		}

		if (resourceName.length() < NameValidator.BLOB_FILE_DIRECTORY_MIN_LENGTH || resourceName.length() > NameValidator.FILE_DIRECTORY_MAX_LENGTH) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME_LENGTH, resourceType, NameValidator.BLOB_FILE_DIRECTORY_MIN_LENGTH, NameValidator.FILE_DIRECTORY_MAX_LENGTH ));
		}

		if (!NameValidator.FILE_DIRECTORY_REGEX.matcher(resourceName).matches()) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME, resourceType));
		}
	}

	/**
	 * Checks if a table name is valid.
	 *
	 * @param tableName
	 *            A String representing the table name to validate.
	 */
	public static void validateTableName(String tableName) {
		if (Utility.isNullOrEmptyOrWhitespace(tableName)) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.RESOURCE_NAME_EMPTY, SR.TABLE));
		}

		if (tableName.length() < NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MIN_LENGTH || tableName.length() > NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MAX_LENGTH) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME_LENGTH, SR.TABLE, NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MIN_LENGTH, NameValidator.CONTAINER_SHARE_QUEUE_TABLE_MAX_LENGTH));
		}

		if (!(NameValidator.TABLE_REGEX.matcher(tableName).matches()
				|| NameValidator.METRICS_TABLE_REGEX.matcher(tableName).matches()
				|| tableName.equalsIgnoreCase("$MetricsCapacityBlob"))) {
			throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.INVALID_RESOURCE_NAME, SR.TABLE));
		}
	}
}


/**
 * 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 shares.
 */
final class ShareListHandler extends DefaultHandler {

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

    private final CloudFileClient serviceClient;

    private final ListResponse<CloudFileShare> response = new ListResponse<CloudFileShare>();
    private FileShareAttributes attributes;
    private String shareName;
    private String snapshotID;

    private ShareListHandler(CloudFileClient serviceClient) {
        this.serviceClient = serviceClient;
    }

    /**
     * Parses a {@link ShareListResponse} form the given XML stream.
     *
     * @param serviceClient
     *            a reference to the client object associated with this object.
     * @param stream
     *            the stream from which to parse the share list
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws IOException
     */
    protected static ListResponse<CloudFileShare> getShareList(final InputStream stream,
            final CloudFileClient serviceClient) throws ParserConfigurationException, SAXException, IOException {
        SAXParser saxParser = Utility.getSAXParser();
        ShareListHandler handler = new ShareListHandler(serviceClient);
        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.SHARE_ELEMENT.equals(localName)) {
            this.shareName = Constants.EMPTY_STRING;
            this.snapshotID = null;
            this.attributes = new FileShareAttributes();
        }
    }

    @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.SHARE_ELEMENT.equals(currentNode)) {
            try {
                CloudFileShare retShare = this.serviceClient.getShareReference(this.shareName);
                retShare.setMetadata(this.attributes.getMetadata());
                retShare.setProperties(this.attributes.getProperties());
                retShare.snapshotID = this.snapshotID;

                this.response.getResults().add(retShare);
            }
            catch (URISyntaxException e) {
                throw new SAXException(e);
            }
            catch (StorageException e) {
                throw new SAXException(e);
            }

        }
        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.SHARE_ELEMENT.equals(parentNode)) {
            if (Constants.NAME_ELEMENT.equals(currentNode)) {
                this.shareName = value;
            }
            else if (Constants.QueryConstants.SNAPSHOT.equals(currentNode.toLowerCase())) {
                this.snapshotID = value;
            }
        }
        else if (Constants.PROPERTIES.equals(parentNode)) {
            try {
                getProperties(currentNode, value);
            }
            catch (ParseException e) {
                throw new SAXException(e);
            }
        }
        else if (Constants.METADATA_ELEMENT.equals(parentNode)) {
            if (value == null) {
                value = "";
            }
            this.attributes.getMetadata().put(currentNode, value);
        }

        this.bld = new StringBuilder();
    }

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

    private void getProperties(String currentNode, String value) throws ParseException {
        if (currentNode.equals(Constants.LAST_MODIFIED_ELEMENT)) {
            this.attributes.getProperties().setLastModified(Utility.parseRFC1123DateFromStringInGMT(value));
        }
        else if (currentNode.equals(Constants.ETAG_ELEMENT)) {
            this.attributes.getProperties().setEtag(Utility.formatETag(value));
        }
        else if (currentNode.equals(FileConstants.SHARE_QUOTA_ELEMENT)) {
            this.attributes.getProperties().setShareQuota(Utility.isNullOrEmpty(value) ? null : Integer.parseInt(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.file;

import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet;

import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultContinuationType;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.ServiceClient;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsAnonymous;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.LazySegmentedIterable;
import com.microsoft.azure.storage.core.ListResponse;
import com.microsoft.azure.storage.core.ListingContext;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SegmentedStorageRequest;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.Utility;

/**
 * Provides a client for accessing the Microsoft Azure File service.
 *

 * This class provides a point of access to the File service. The service client encapsulates the base URI for the File
 * service. It also encapsulates the credentials for accessing the storage account.
 */
public final class CloudFileClient extends ServiceClient {

    /**
     * Holds the default request option values associated with this Service Client.
     */
    private FileRequestOptions defaultRequestOptions = new FileRequestOptions();

    /**
     * Creates an instance of the CloudFileClient class using the specified File service endpoint and
     * account credentials.
     *
     * @param baseUri
     *            A java.net.URI object that represents the File service endpoint used to create the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the account credentials.
     */
    public CloudFileClient(final URI baseUri, StorageCredentials credentials) {
        this(new StorageUri(baseUri), credentials);
    }

    /**
     * Creates an instance of the CloudFileClient class using the specified File service endpoint and
     * account credentials.
     *
     * @param storageUri
     *            A {@link StorageUri} object that represents the File service endpoint used to create the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the account credentials.
     */
    public CloudFileClient(StorageUri storageUri, StorageCredentials credentials) {
        super(storageUri, credentials);
        if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) {
            throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS);
        }
        FileRequestOptions.applyDefaults(this.defaultRequestOptions);
    }

    /**
     * Gets a {@link CloudFileShare} object with the specified name.
     *
     * @param shareName
     *            The name of the share, which must adhere to share naming rules. The share name should not
     *            include any path separator characters (/).
     *            Share names must be lowercase, between 3-63 characters long and must start with a letter or
     *            number. Share names may contain only letters, numbers, and the dash (-) character.
     *
     * @return A reference to a {@link CloudFileShare} object.
     * @throws StorageException
     * @throws URISyntaxException
     *
     * @see Naming and Referencing Shares,
     *      Directories, Files, and Metadata
     */
    public CloudFileShare getShareReference(final String shareName) throws URISyntaxException, StorageException {
        Utility.assertNotNullOrEmpty("shareName", shareName);
        return this.getShareReference(shareName, null);
    }

    /**
     * Gets a {@link CloudFileShare} object with the specified name.
     *
     * @param shareName
     *            The name of the share, which must adhere to share naming rules. The share name should not
     *            include any path separator characters (/).
     *            Share names must be lowercase, between 3-63 characters long and must start with a letter or
     *            number. Share names may contain only letters, numbers, and the dash (-) character.
     * @param snapshotID
     *            A String that represents the snapshot ID of the share.
     * @return A reference to a {@link CloudFileShare} object.
     * @throws StorageException
     * @throws URISyntaxException
     *
     * @see Naming and Referencing Shares,
     *      Directories, Files, and Metadata
     */
    public CloudFileShare getShareReference(final String shareName, String snapshotID) throws URISyntaxException, StorageException {
        Utility.assertNotNullOrEmpty("shareName", shareName);
        return new CloudFileShare(shareName, snapshotID, this);
    }

    /**
     * Returns an enumerable collection of shares for this File service client.
     *
     * @return An enumerable collection of {@link CloudFileShare} objects retrieved lazily that represent the
     *         shares for this client.
     */
    @DoesServiceRequest
    public Iterable<CloudFileShare> listShares() {
        return this.listSharesWithPrefix(null, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */);
    }

    /**
     * Returns an enumerable collection of shares whose names begin with the specified prefix for this File service
     * client.
     *
     * @param prefix
     *            A String that represents the share name prefix.
     *
     * @return An enumerable collection of {@link CloudFileShare} objects retrieved lazily that represent the
     *         shares for this client whose names begin with the specified prefix.
     */
    @DoesServiceRequest
    public Iterable<CloudFileShare> listShares(final String prefix) {
        return this.listSharesWithPrefix(prefix, EnumSet.noneOf(ShareListingDetails.class), null /* options */, null /* opContext */);
    }

    /**
     * Returns an enumerable collection of shares whose names begin with the specified prefix for this File
     * service client, using the specified details settings, request options, and operation context.
     *
     * @param prefix
     *            A String that represents the share name prefix.
     * @param detailsIncluded
     *            A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate
     *            whether share snapshots and/or metadata will be returned.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return An enumerable collection of {@link CloudFileShare} objects retrieved lazily that represents the
     *         shares for this client.
     */
    @DoesServiceRequest
    public Iterable<CloudFileShare> listShares(final String prefix, final EnumSet<ShareListingDetails> detailsIncluded,
            final FileRequestOptions options, final OperationContext opContext) {
        return this.listSharesWithPrefix(prefix, detailsIncluded, options, opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of shares for this File service client.
     *
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link CloudFileShare} objects that represent the shares for this client.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<CloudFileShare> listSharesSegmented() throws StorageException {
        return this.listSharesSegmented(null, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */,
                null /* options */, null /* opContext */);
    }

    /**
     * Returns a result segment of an enumerable collection of shares whose names begin with the specified
     * prefix for this File service client.
     *
     * @param prefix
     *            A String that represents the prefix of the share name.
     *
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link CloudFileShare} objects that represent the shares whose names begin with the specified
     *         prefix.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<CloudFileShare> listSharesSegmented(final String prefix) throws StorageException {
        return this.listSharesWithPrefixSegmented(prefix, EnumSet.noneOf(ShareListingDetails.class), null, null /* continuationToken */,
                null /* options */, null /* opContext */);
    }

    /**
     * Returns a result segment of an enumerable collection of shares whose names begin with the specified
     * prefix, using the specified listing details options, request options, and operation context.
     *
     * @param prefix
     *            A String that represents the prefix of the share name.
     * @param detailsIncluded
     *            A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate
     *            whether share snapshots and/or metadata will be returned.
     * @param maxResults
     *            The maximum number of results to retrieve.  If null or greater
     *            than 5000, the server will return up to 5,000 items.  Must be at least 1.
     * @param continuationToken
     *            A {@link ResultContinuation} object that represents a continuation token returned
     *            by a previous listing operation.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for
     *            the request. Specifying null will use the default request options
     *            from the associated service client ( {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current
     *            operation. This object is used to track requests to the storage service,
     *            and to provide additional runtime information about the operation.
     *
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection
     *         of {@link CloudFileShare} objects that represent the shares for this client.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<CloudFileShare> listSharesSegmented(final String prefix,
            final EnumSet<ShareListingDetails> detailsIncluded, final Integer maxResults,
            final ResultContinuation continuationToken, final FileRequestOptions options,
            final OperationContext opContext) throws StorageException {
        return this.listSharesWithPrefixSegmented(prefix, detailsIncluded, maxResults, continuationToken, options,
                opContext);
    }

    /**
     * Returns an enumerable collection of shares whose names begin with the specified prefix, using the
     * specified details settings, request options, and operation context.
     *
     * @param prefix
     *            A String that represents the prefix of the share name.
     * @param detailsIncluded
     *            A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate
     *            whether share snapshots and/or metadata will be returned.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return An enumerable collection of {@link CloudFileShare} objects retrieved lazily that represent the
     *         shares whose names begin with the specified prefix.
     */
    private Iterable<CloudFileShare> listSharesWithPrefix(final String prefix,
            final EnumSet<ShareListingDetails> detailsIncluded, FileRequestOptions options, OperationContext opContext) {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();

        return new LazySegmentedIterable<CloudFileClient, Void, CloudFileShare>(this.listSharesWithPrefixSegmentedImpl(
                prefix, detailsIncluded, null, options, segmentedRequest), this, null, options.getRetryPolicyFactory(),
                opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of shares whose names begin with the specified
     * prefix, using the specified listing details options, request options, and operation context.
     *
     * @param prefix
     *            A String that represents the prefix of the share name.
     * @param detailsIncluded
     *            A java.util.EnumSet object that contains {@link ShareListingDetails} values that indicate
     *            whether share snapshots and/or metadata will be returned.
     * @param maxResults
     *            The maximum number of results to retrieve.  If null or greater
     *            than 5000, the server will return up to 5,000 items.  Must be at least 1.
     * @param continuationToken
     *            A {@link ResultContinuation} object that represents a continuation token returned
     *            by a previous listing operation.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for
     *            the request. Specifying null will use the default request options
     *            from the associated service client ( {@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current
     *            operation. This object is used to track requests to the storage service,
     *            and to provide additional runtime information about the operation.
     *
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection
     *         of {@link CloudFileShare} objects that represent the shares for this client.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    private ResultSegment<CloudFileShare> listSharesWithPrefixSegmented(final String prefix,
            final EnumSet<ShareListingDetails> detailsIncluded, final Integer maxResults,
            final ResultContinuation continuationToken, FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        Utility.assertContinuationType(continuationToken, ResultContinuationType.SHARE);

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

        return ExecutionEngine.executeWithRetry(this, null,
                this.listSharesWithPrefixSegmentedImpl(prefix, detailsIncluded, maxResults, options, segmentedRequest),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudFileClient, Void, ResultSegment<CloudFileShare>> listSharesWithPrefixSegmentedImpl(
            final String prefix, final EnumSet<ShareListingDetails> detailsIncluded, final Integer maxResults,
            final FileRequestOptions options, final SegmentedStorageRequest segmentedRequest) {

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

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

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

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

            @Override
            public HttpURLConnection buildRequest(CloudFileClient client, Void parentObject, OperationContext context)
                    throws Exception {
                listingContext.setMarker(segmentedRequest.getToken() != null ? segmentedRequest.getToken()
                        .getNextMarker() : null);
                return FileRequest.listShares(
                        client.getCredentials().transformUri(client.getStorageUri()).getUri(this.getCurrentLocation()),
                        options, context, listingContext, detailsIncluded);
            }

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

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

            @Override
            public ResultSegment<CloudFileShare> postProcessResponse(HttpURLConnection connection, Void share,
                    CloudFileClient client, OperationContext context, ResultSegment<CloudFileShare> storageObject)
                    throws Exception {
                final ListResponse<CloudFileShare> response = ShareListHandler.getShareList(this.getConnection()
                        .getInputStream(), client);
                ResultContinuation newToken = null;

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

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

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

        return getRequest;
    }

    /**
     * Retrieves the current {@link FileServiceProperties} for the given storage service. This encapsulates
     * the CORS configurations.
     *
     * @return A {@link FileServiceProperties} object representing the current configuration of the service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final FileServiceProperties downloadServiceProperties() throws StorageException {
        return this.downloadServiceProperties(null /* options */, null /* opContext */);
    }

    /**
     * Retrieves the current {@link FileServiceProperties} for the given storage service. This encapsulates
     * the CORS configurations.
     *
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client
     *            ({@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return A {@link FileServiceProperties} object representing the current configuration of the service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final FileServiceProperties downloadServiceProperties(FileRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        return new FileServiceProperties(ExecutionEngine.executeWithRetry(
                this, null, this.downloadServicePropertiesImpl(options, false),
                options.getRetryPolicyFactory(), opContext));
    }

    /**
     * Uploads a new {@link FileServiceProperties} configuration to the given storage service. This encapsulates
     * the CORS configurations.
     *
     * @param properties
     *            A {@link FileServiceProperties} object which specifies the service properties to upload.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadServiceProperties(final FileServiceProperties properties) throws StorageException {
        this.uploadServiceProperties(properties, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a new {@link FileServiceProperties} configuration to the given storage service. This encapsulates
     * the CORS configurations.
     *
     * @param properties
     *            A {@link FileServiceProperties} object which specifies the service properties to upload.
     * @param options
     *            A {@link FileRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client
     *            ({@link CloudFileClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadServiceProperties(final FileServiceProperties properties, FileRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        Utility.assertNotNull("properties", properties);

        ExecutionEngine.executeWithRetry(this, null,
                this.uploadServicePropertiesImpl(properties.getServiceProperties(), options, opContext, false),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Gets the {@link FileRequestOptions} that is used for requests associated with this CloudFileClient
     *
     * @return The {@link FileRequestOptions} object containing the values used by this CloudFileClient
     */
    @Override
    public FileRequestOptions getDefaultRequestOptions() {
        return this.defaultRequestOptions;
    }

    /**
     * Sets the {@link FileRequestOptions} that is used for any requests associated with this
     * CloudFileClient object.
     *
     * @param defaultRequestOptions
     *            A {@link FileRequestOptions} object which specifies the options to use.
     */
    public void setDefaultRequestOptions(FileRequestOptions defaultRequestOptions) {
        Utility.assertNotNull("defaultRequestOptions", defaultRequestOptions);
        this.defaultRequestOptions = defaultRequestOptions;
    }

    /**
     * Indicates whether path-style URIs are being used.
     *
     * @return true if using path-style URIs; otherwise, false.
     */
    @Override
    protected boolean isUsePathStyleUris() {
        return super.isUsePathStyleUris();
    }
}


/**
 * 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.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

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.SR;

/**
 * The {@link TableServiceEntity} class represents the base object type for a table entity in the Storage service.
 * {@link TableServiceEntity} provides a base implementation for the {@link TableEntity} interface that provides
 * readEntity and writeEntity methods that by default serialize and deserialize all properties
 * via reflection. A table entity class may extend this class and override the readEntity and
 * writeEntity methods to provide customized or more performant serialization logic.
 *

 * The use of reflection allows subclasses of {@link TableServiceEntity} to be serialized and deserialized without
 * having to implement the serialization code themselves. When both a getter method and setter method are found for a
 * given property name and data type, then the appropriate method is invoked automatically to serialize or deserialize
 * the data. To take advantage of the automatic serialization code, your table entity classes should provide getter and
 * setter methods for each property in the corresponding table entity in Microsoft Azure table storage. The reflection
 * code looks for getter and setter methods in pairs of the form
 *

 * public type getPropertyName() { ... }
 *

 * and
 *

 * public void setPropertyName(type parameter) { ... }
 *

 * where PropertyName is a property name for the table entity, and type is compatible with
 * the EDM data type of the property. See the table below for a map of property types to their language equivalents. The
 * {@link StoreAs} annotation may be applied with a name attribute to specify a property name for
 * reflection on getter and setter methods that do not follow the property name convention. Method names and the
 * name attribute of {@link StoreAs} annotations are case sensitive for matching property names with
 * reflection. Use the {@link Ignore} annotation to prevent methods from being used by reflection for automatic
 * serialization and deserialization. Note that the names "PartitionKey", "RowKey", "Timestamp", and "Etag" are reserved
 * and will be ignored if set with the {@link StoreAs} annotation in a subclass.
 *

 * The following table shows the supported property data types in Microsoft Azure storage and the corresponding Java
 * types when deserialized.
 *

 *

 * Storage Type
 * EdmType Value
 * Java Type
 * Description
 *

 *

 * Edm.Binary
 * {@link EdmType#BINARY}
 * byte[], Byte[]
 * An array of bytes up to 64 KB in size.
 *

 *

 * Edm.Boolean
 * {@link EdmType#BOOLEAN}
 * boolean, Boolean
 * A Boolean value.
 *

 *

 * Edm.DateTime
 * {@link EdmType#DATE_TIME}
 * java.util.Date
 * A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight,
 * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.
 *

 *

 * Edm.Double
 * {@link EdmType#DOUBLE}
 * double, Double
 * A 64-bit double-precision floating point value.
 *

 *

 * Edm.Guid
 * {@link EdmType#GUID}
 * UUID
 * A 128-bit globally unique identifier.
 *

 *

 * Edm.Int32
 * {@link EdmType#INT32}
 * int, Integer
 * A 32-bit integer value.
 *

 *

 * Edm.Int64
 * {@link EdmType#INT64}
 * long, Long
 * A 64-bit integer value.
 *

 *

 * Edm.String
 * {@link EdmType#STRING}
 * String
 * A UTF-16-encoded value. String values may be up to 64 KB in size.
 *

 *

 *

 * See the MSDN topic Understanding the Table Service
 * Data Model for an overview of tables, entities, and properties as used in the Microsoft Azure Storage service.
 *

 * For an overview of the available EDM primitive data types and names, see the
 *
 * Primitive Data Types section of
 * the OData Protocol Overview.
 *

 *
 * @see EdmType
 */
public class TableServiceEntity implements TableEntity {
    /**
     * Deserializes the table entity property map into the specified object instance using reflection.
     *

     * This static method takes an object instance that represents a table entity type and uses reflection on its class
     * type to find methods to deserialize the data from the property map into the instance.
     *

     * Each property name and data type in the properties map is compared with the methods in the class type for a pair
     * of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with
     * names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set
     * with the property name. The methods must have return types or parameter data types that match the data type of
     * the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied into the instance
     * object by invoking the setter method on the instance. Properties that do not match a method pair by name and data
     * type are not copied.
     *
     * @param instance
     *            An Object reference to an instance of a class implementing {@link TableEntity} to
     *            deserialize the table entity
     *            data into.
     * @param properties
     *            A java.util.HashMap object which maps String property names to
     *            {@link EntityProperty} objects containing typed data
     *            values to deserialize into the instance parameter object.
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation.
     *
     * @throws IllegalArgumentException
     *             if the table entity response received is invalid or improperly formatted.
     * @throws IllegalAccessException
     *             if the table entity threw an exception during deserialization.
     * @throws InvocationTargetException
     *             if a method invoked on the instance parameter threw an exception during deserialization.
     */
    public static void readEntityWithReflection(final Object instance,
            final HashMap<String, EntityProperty> properties, final OperationContext opContext)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass());

        for (final Entry<String, EntityProperty> p : properties.entrySet()) {
            if (props.containsKey(p.getKey())) {
                props.get(p.getKey()).consumeEntityProperty(p.getValue(), instance);
            }
        }
    }

    /**
     * Serializes the property data from a table entity instance into a property map using reflection.
     *

     * This static method takes an object instance that represents a table entity type and uses reflection on its class
     * type to find methods to serialize the data from the instance into the property map.
     *

     * Each property name and data type in the properties map is compared with the methods in the class type for a pair
     * of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with
     * names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set
     * with the property name. The methods must have return types or parameter data types that match the data type of
     * the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied from the instance
     * object by invoking the getter method on the instance. Properties that do not have a method pair with matching
     * name and data type are not copied.
     *
     * @param instance
     *            An Object reference to an instance of a class implementing {@link TableEntity} to
     *            serialize the table entity
     *            data from.
     * @return
     *         A java.util.HashMap object which maps String property names to
     *         {@link EntityProperty} objects containing typed data values serialized from the instance parameter
     *         object.
     *
     * @throws IllegalArgumentException
     *             if the table entity is invalid or improperly formatted.
     * @throws IllegalAccessException
     *             if the table entity threw an exception during serialization.
     * @throws InvocationTargetException
     *             if a method invoked on the instance parameter threw an exception during serialization.
     */
    public static HashMap<String, EntityProperty> writeEntityWithReflection(final Object instance)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass());

        final HashMap<String, EntityProperty> retVal = new HashMap<String, EntityProperty>();
        for (final Entry<String, PropertyPair> p : props.entrySet()) {
            retVal.put(p.getValue().effectiveName, p.getValue().generateEntityProperty(instance));
        }

        return retVal;
    }

    /**
     * The default to multiply the number of CPU's by to get the number of threads to allow in the
     * ReflectedEntityCache
     */
    private static final int DEFAULT_CONCURRENCY_MULTIPLIER = 4;

    /**
     * The default load factor for the ReflectedEntityCache
     */
    private static final float DEFAULT_LOAD_FACTOR = (float) .75;

    /**
     * The default initial capacity for the ReflectedEntityCache
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 31;

    /**
     * The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
     * than using reflection on a known entity type, the values from the dictionary are used instead.
     */
    private static boolean disableReflectedEntityCache = false;

    /**
     * Reserved for internal use. The value of the partition key in the entity.
     */
    protected String partitionKey = null;

    /**
     * Reserved for internal use. The value of the row key in the entity.
     */
    protected String rowKey = null;

    /**
     * Reserved for internal use. The value of the ETag for the entity.
     */
    protected String etag = null;

    /**
     * Reserved for internal use. The value of the Timestamp in the entity.
     */
    protected Date timeStamp = new Date();

    /**
     * Initializes an empty {@link TableServiceEntity} instance.
     */
    public TableServiceEntity() {
        // Empty ctor
    }

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

    /**
     * Gets the ETag value to verify for the entity. 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.
     *
     * @return
     *         A String containing the ETag for the entity.
     */
    @Override
    public String getEtag() {
        return this.etag;
    }

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

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

    /**
     * Gets the Timestamp for the entity. The server manages the value of Timestamp, which cannot be modified.
     *
     * @return
     *         A java.util.Date object which represents the Timestamp value for the entity.
     */
    @Override
    public Date getTimestamp() {
        return this.timeStamp;
    }

    /**
     * Gets a value indicating whether or not the reflected entity cache is disabled. For most scenarios, disabling
     * the reflected entity cache is not recommended due to its effect on performance.
     *
     * The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
     * than using reflection on a known entity type, the values from the dictionary are used instead.
     *
     * @return
     *         true if the reflected entity cache is disabled; otherwise, false.
     */
    public static boolean isReflectedEntityCacheDisabled() {
        return TableServiceEntity.disableReflectedEntityCache;
    }

    /**
     * Sets a boolean representing whether or not the reflected entity cache is disabled. For most scenarios, disabling
     * the reflected entity cache is not recommended due to its effect on performance.
     *
     * The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
     * than using reflection on a known entity type, the values from the dictionary are used instead.
     *
     * @param disableReflectedEntityCache
     *            true to disable the reflected entity cache; otherwise, false.
     */
    public static void setReflectedEntityCacheDisabled(boolean disableReflectedEntityCache) {
        if (TableServiceEntity.reflectedEntityCache != null && disableReflectedEntityCache) {
            TableServiceEntity.reflectedEntityCache.clear();
        }

        TableServiceEntity.disableReflectedEntityCache = disableReflectedEntityCache;
    }

    /**
     * Populates this table entity instance using the map of property names to {@link EntityProperty} data typed values.
     *

     * This method invokes {@link TableServiceEntity#readEntityWithReflection} to populate the table entity instance the
     * method is called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take
     * advantage of this behavior by implementing getter and setter methods for the particular properties of the table
     * entity in Microsoft Azure storage the class represents.
     *

     * Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code.
     *
     * @param properties
     *            The java.util.HashMap of String property names to {@link EntityProperty}
     *            data values to deserialize and store in this table entity instance.
     * @param opContext
     *            An {@link OperationContext} object used to track the execution of the operation.
     * @throws StorageException
     *             if an error occurs during the deserialization.
     */
    @Override
    public void readEntity(final HashMap<String, EntityProperty> properties, final OperationContext opContext)
            throws StorageException {
        try {
            readEntityWithReflection(this, properties, opContext);
        }
        catch (IllegalArgumentException e) {
            throw new StorageException(StorageErrorCodeStrings.INVALID_DOCUMENT, SR.RESPONSE_RECEIVED_IS_INVALID,
                    Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
        }
        catch (IllegalAccessException e) {
            throw new StorageException(StorageErrorCodeStrings.INVALID_DOCUMENT,
                    SR.EXCEPTION_THROWN_DURING_DESERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
        }
        catch (InvocationTargetException e) {
            throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
                    SR.EXCEPTION_THROWN_DURING_DESERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
        }
    }

    /**
     * Sets the ETag value to verify for the entity. 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 etag
     *            A String containing the ETag for the entity.
     */
    @Override
    public void setEtag(final String etag) {
        this.etag = etag;
    }

    /**
     * Sets the PartitionKey value for the entity.
     *
     * @param partitionKey
     *            A String containing the PartitionKey value for the entity.
     */
    @Override
    public void setPartitionKey(final String partitionKey) {
        this.partitionKey = partitionKey;
    }

    /**
     * Sets the RowKey value for the entity.
     *
     * @param rowKey
     *            A String containing the RowKey value for the entity.
     */
    @Override
    public void setRowKey(final String rowKey) {
        this.rowKey = rowKey;
    }

    /**
     * Sets the timeStamp value for the entity. Note that the timestamp property is a read-only property,
     * set by the service only.
     *
     * @param timeStamp
     *            A java.util.Date containing the timeStamp value for the entity.
     */
    @Override
    public void setTimestamp(final Date timeStamp) {
        this.timeStamp = timeStamp;
    }

    /**
     * Returns a map of property names to {@link EntityProperty} data typed values created by serializing this table
     * entity instance.
     *

     * This method invokes {@link #writeEntityWithReflection} to serialize the table entity instance the method is
     * called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take advantage of
     * this behavior by implementing getter and setter methods for the particular properties of the table entity in
     * Microsoft Azure storage the class represents. Note that the property names "PartitionKey", "RowKey", and
     * "Timestamp" are reserved and will be ignored if set on other methods with the {@link StoreAs} annotation.
     *

     * Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code.
     *
     * @param opContext
     *            An {@link OperationContext} object used to track the execution of the operation.
     * @return
     *         A java.util.HashMap of String property names to {@link EntityProperty} data
     *         typed values representing the properties serialized from this table entity instance.
     * @throws StorageException
     *             if an error occurs during the serialization.
     */
    @Override
    public HashMap<String, EntityProperty> writeEntity(final OperationContext opContext) throws StorageException {
        try {
            return writeEntityWithReflection(this);
        }
        catch (final IllegalAccessException e) {
            throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
                    SR.ATTEMPTED_TO_SERIALIZE_INACCESSIBLE_PROPERTY, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
        }
        catch (final InvocationTargetException e) {
            throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
                    SR.EXCEPTION_THROWN_DURING_SERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
        }
    }

    /**
     * The reflected entity cache caches known entity types and their respective reflected entity dictionaries when
     * entities are deserialized and the payload does not include JSON metadata.
     */
    private static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> reflectedEntityCache = initialize();

    private static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> initialize() {
        Runtime runtime = Runtime.getRuntime();
        int numberOfProcessors = runtime.availableProcessors();
        return new ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>>(DEFAULT_INITIAL_CAPACITY,
                DEFAULT_LOAD_FACTOR, numberOfProcessors * DEFAULT_CONCURRENCY_MULTIPLIER);
    }

    /**
     * The reflected entity cache caches known entity types and their respective reflected entity dictionaries when
     * entities are deserialized and the payload does not include JSON metadata.
     *
     * @return
     *         The ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> representing the known entity
     *         types and their reflected entity dictionaries
     */
    protected static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> getReflectedEntityCache() {
        return TableServiceEntity.reflectedEntityCache;
    }
}


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

import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.microsoft.azure.storage.Constants;

/**
 * RESERVED FOR INTERNAL USE. The base response class for the protocol layer
 */
public class BaseResponse {
    /**
     * Gets the ContentMD5
     *
     * @param request
     *            The response from server.
     * @return The ContentMD5.
     */
    public static String getContentMD5(final HttpURLConnection request) {
        return request.getHeaderField(Constants.HeaderConstants.CONTENT_MD5);
    }

    /**
     * Gets the Date
     *
     * @param request
     *            The response from server.
     * @return The Date.
     */
    public static String getDate(final HttpURLConnection request) {
        final String retString = request.getHeaderField("Date");
        return retString == null ? request.getHeaderField(Constants.HeaderConstants.DATE) : retString;
    }

    /**
     * Gets the Etag
     *
     * @param request
     *            The response from server.
     * @return The Etag.
     */
    public static String getEtag(final HttpURLConnection request) {
        return request.getHeaderField(Constants.HeaderConstants.ETAG);
    }

    /**
     * Gets the metadata from the request The response from server.
     *
     * @return the metadata from the request
     */
    public static HashMap<String, String> getMetadata(final HttpURLConnection request) {
        return getValuesByHeaderPrefix(request, Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA);
    }

    /**
     * Gets the request id.
     *
     * @param request
     *            The response from server.
     * @return The request ID.
     */
    public static String getRequestId(final HttpURLConnection request) {
        return request.getHeaderField(Constants.HeaderConstants.REQUEST_ID_HEADER);
    }

    /**
     * Gets if the request was encrypted by the server.
     * @param request
     *            The response from the server.
     * @return A boolean indicating if the request was encrypted by the server.
     */
    public static boolean isServerRequestEncrypted(HttpURLConnection request) {
        return Constants.TRUE.equals(request.getHeaderField(Constants.HeaderConstants.SERVER_REQUEST_ENCRYPTED));
    }

    /**
     * Returns all the header/value pairs with the given prefix.
     *
     * @param request
     *            the request object containing headers to parse.
     * @param prefix
     *            the prefix for headers to be returned.
     * @return all the header/value pairs with the given prefix.
     */
    private static HashMap<String, String> getValuesByHeaderPrefix(final HttpURLConnection request, final String prefix) {
        final HashMap<String, String> retVals = new HashMap<String, String>();
        final Map<String, List<String>> headerMap = request.getHeaderFields();
        final int prefixLength = prefix.length();

        for (final Entry<String, List<String>> entry : headerMap.entrySet()) {
            if (entry.getKey() != null && entry.getKey().startsWith(prefix)) {
                final List<String> currHeaderValues = entry.getValue();
                retVals.put(entry.getKey().substring(prefixLength), currHeaderValues.get(0));
            }
        }

        return retVals;
    }

    /**
     * Private Default Ctor
     */
    protected BaseResponse() {
        // No op
    }
}