PathsHelper.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.implementation;

import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
import com.azure.cosmos.implementation.apachecommons.lang.tuple.Pair;
import com.azure.cosmos.implementation.apachecommons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Used internally to provide utility methods to work with the resource's path in the Azure Cosmos DB database service.
 */
public class PathsHelper {
    private final static Logger logger = LoggerFactory.getLogger(PathsHelper.class);

    public static String generatePath(ResourceType resourceType, RxDocumentServiceRequest request, boolean isFeed) {
        if (request.getIsNameBased()) {
            return generatePathForNameBased(resourceType, request.getResourceAddress(), isFeed, request.getOperationType());
        } else {
            return generatePath(resourceType, request.getResourceId(), isFeed, request.getOperationType());
        }
    }

    public static String generatePathForNameBased(Resource resourceType, String resourceOwnerFullName, String resourceName) {
        if (resourceName == null)
            return null;

        if (resourceType instanceof Database) {
            return Paths.DATABASES_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceOwnerFullName == null) {
            return null;
        } else if (resourceType instanceof DocumentCollection) {
            return resourceOwnerFullName + "/" + Paths.COLLECTIONS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof StoredProcedure) {
            return resourceOwnerFullName + "/" + Paths.STORED_PROCEDURES_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof UserDefinedFunction) {
            return resourceOwnerFullName + "/" + Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Trigger) {
            return resourceOwnerFullName + "/" + Paths.TRIGGERS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Conflict) {
            return resourceOwnerFullName + "/" + Paths.CONFLICTS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof User) {
            return resourceOwnerFullName + "/" + Paths.USERS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Permission) {
            return resourceOwnerFullName + "/" + Paths.PERMISSIONS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Document) {
            return resourceOwnerFullName + "/" + Paths.DOCUMENTS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Offer) {
            return Paths.OFFERS_PATH_SEGMENT + "/" + resourceName;
        } else if (resourceType instanceof Resource) {
            // just generic Resource type.
            return null;
        }

        String errorMessage = String.format(RMResources.UnknownResourceType, resourceType.toString());
        assert false : errorMessage;
        throw new IllegalArgumentException(errorMessage);
    }

    private static String generatePathForNameBased(ResourceType resourceType, String resourceFullName, boolean isFeed, OperationType operationType) {
        if (isFeed && Strings.isNullOrEmpty(resourceFullName) && resourceType != ResourceType.Database) {
            String errorMessage = String.format(RMResources.UnexpectedResourceType, resourceType);
            throw new IllegalArgumentException(errorMessage);
        }

        String resourcePath = null;
        if (resourceType == ResourceType.PartitionKey && operationType == OperationType.Delete) {
            resourcePath = resourceFullName + "/" + Paths.OPERATIONS_PATH_SEGMENT + "/" + Paths.PARTITION_KEY_DELETE_PATH_SEGMENT;
        } else if (!isFeed) {
            resourcePath = resourceFullName;
        } else if (resourceType == ResourceType.Database) {
            return Paths.DATABASES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.DocumentCollection) {
            resourcePath = resourceFullName + "/" + Paths.COLLECTIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.StoredProcedure) {
            resourcePath = resourceFullName + "/" + Paths.STORED_PROCEDURES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.UserDefinedFunction) {
            resourcePath = resourceFullName + "/" + Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Trigger) {
            resourcePath = resourceFullName + "/" + Paths.TRIGGERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Conflict) {
            resourcePath = resourceFullName + "/" + Paths.CONFLICTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Attachment) {
            resourcePath = resourceFullName + "/" + Paths.ATTACHMENTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.User) {
            resourcePath = resourceFullName + "/" + Paths.USERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Permission) {
            resourcePath = resourceFullName + "/" + Paths.PERMISSIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Document) {
            resourcePath = resourceFullName + "/" + Paths.DOCUMENTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Offer) {
            return resourceFullName + "/" + Paths.OFFERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.PartitionKeyRange) {
            return resourceFullName + "/" + Paths.PARTITION_KEY_RANGES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Schema) {
            resourcePath = resourceFullName + "/" + Paths.SCHEMAS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.ClientEncryptionKey) {
            resourcePath = resourceFullName + "/" + Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT;
        } else {
            String errorMessage = String.format(RMResources.UnknownResourceType, resourceType.toString());
            assert false : errorMessage;
            throw new IllegalArgumentException(errorMessage);
        }

        return resourcePath;
    }

    public static String generatePath(ResourceType resourceType, String ownerOrResourceId, boolean isFeed) {
        if (resourceType == ResourceType.PartitionKey) {
            return generatePath(resourceType, ownerOrResourceId, isFeed, OperationType.Delete);
        } else {
            return generatePath(resourceType, ownerOrResourceId, isFeed, null);
        }
    }

    private static String generatePath(ResourceType resourceType, String ownerOrResourceId, boolean isFeed, OperationType operationType) {
        if (isFeed && (ownerOrResourceId == null || ownerOrResourceId.isEmpty()) &&
            resourceType != ResourceType.Database &&
            resourceType != ResourceType.Offer &&
            resourceType != ResourceType.MasterPartition &&
            resourceType != ResourceType.ServerPartition &&
            resourceType != ResourceType.DatabaseAccount &&
                resourceType != ResourceType.Topology) {
            throw new IllegalStateException("INVALID resource type");
        }

        if(ownerOrResourceId == null) {
            ownerOrResourceId = StringUtils.EMPTY;
        }

        if (isFeed && resourceType == ResourceType.Database) {
            return Paths.DATABASES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Database) {
            return Paths.DATABASES_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (isFeed && resourceType == ResourceType.DocumentCollection) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.DocumentCollection) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString();
        } else if (isFeed && resourceType == ResourceType.Offer) {
            return Paths.OFFERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Offer) {
            return Paths.OFFERS_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (isFeed && resourceType == ResourceType.StoredProcedure) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.STORED_PROCEDURES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.StoredProcedure) {
            ResourceId storedProcedureId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + storedProcedureId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + storedProcedureId.getDocumentCollectionId().toString() + "/" +
                    Paths.STORED_PROCEDURES_PATH_SEGMENT + "/" + storedProcedureId.getStoredProcedureId().toString();
        } else if (isFeed && resourceType == ResourceType.UserDefinedFunction) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.UserDefinedFunction) {
            ResourceId functionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + functionId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + functionId.getDocumentCollectionId().toString() + "/" +
                    Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT + "/" + functionId.getUserDefinedFunctionId().toString();
        } else if (isFeed && resourceType == ResourceType.Trigger) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.TRIGGERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Trigger) {
            ResourceId triggerId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + triggerId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + triggerId.getDocumentCollectionId().toString() + "/" +
                    Paths.TRIGGERS_PATH_SEGMENT + "/" + triggerId.getTriggerId().toString();
        } else if (isFeed && resourceType == ResourceType.Conflict) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.CONFLICTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Conflict) {
            ResourceId conflictId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + conflictId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + conflictId.getDocumentCollectionId().toString() + "/" +
                    Paths.CONFLICTS_PATH_SEGMENT + "/" + conflictId.getConflictId().toString();
        } else if (isFeed && resourceType == ResourceType.PartitionKeyRange) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" +
                    documentCollectionId.getDocumentCollectionId().toString() + "/" +
                    Paths.PARTITION_KEY_RANGES_PATH_SEGMENT;
        } else if (resourceType == ResourceType.PartitionKeyRange) {
            ResourceId partitionKeyRangeId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + partitionKeyRangeId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + partitionKeyRangeId.getDocumentCollectionId().toString() + "/" +
                    Paths.PARTITION_KEY_RANGES_PATH_SEGMENT + "/" + partitionKeyRangeId.getPartitionKeyRangeId().toString();
        } else if (isFeed && resourceType == ResourceType.Attachment) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.DOCUMENTS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentId().toString() + "/" +
                            Paths.ATTACHMENTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Attachment) {
            ResourceId attachmentId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + attachmentId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + attachmentId.getDocumentCollectionId().toString() + "/" +
                    Paths.DOCUMENTS_PATH_SEGMENT + "/" + attachmentId.getDocumentId().toString() + "/" +
                    Paths.ATTACHMENTS_PATH_SEGMENT + "/" + attachmentId.getAttachmentId().toString();
        } else if (isFeed && resourceType == ResourceType.User) {
            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + ownerOrResourceId + "/" +
                            Paths.USERS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.User) {
            ResourceId userId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + userId.getDatabaseId().toString() + "/" +
                    Paths.USERS_PATH_SEGMENT + "/" + userId.getUserId().toString();
        } else if (isFeed && resourceType == ResourceType.Permission) {
            ResourceId userId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + userId.getDatabaseId().toString() + "/" +
                            Paths.USERS_PATH_SEGMENT + "/" + userId.getUserId().toString() + "/" +
                            Paths.PERMISSIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Permission) {
            ResourceId permissionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + permissionId.getDatabaseId().toString() + "/" +
                    Paths.USERS_PATH_SEGMENT + "/" + permissionId.getUserId().toString() + "/" +
                    Paths.PERMISSIONS_PATH_SEGMENT + "/" + permissionId.getPermissionId().toString();
        } else if (isFeed && resourceType == ResourceType.Document) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return
                    Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                            Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                            Paths.DOCUMENTS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Document) {
            ResourceId documentId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + documentId.getDatabaseId().toString() + "/" +
                    Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentId.getDocumentCollectionId().toString() + "/" +
                    Paths.DOCUMENTS_PATH_SEGMENT + "/" + documentId.getDocumentId().toString();
        } else if (isFeed && resourceType == ResourceType.MasterPartition) {
            return Paths.PARTITIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.MasterPartition) {
            return Paths.PARTITIONS_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (isFeed && resourceType == ResourceType.ServerPartition) {
            return Paths.PARTITIONS_PATH_SEGMENT;
        } else if (resourceType == ResourceType.ServerPartition) {
            return Paths.PARTITIONS_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (isFeed && resourceType == ResourceType.Topology) {
            return Paths.TOPOLOGY_PATH_SEGMENT;
        } else if (resourceType == ResourceType.Topology) {
            return Paths.TOPOLOGY_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (isFeed && resourceType == ResourceType.DatabaseAccount) {
            return Paths.DATABASE_ACCOUNT_PATH_SEGMENT;
        } else if (resourceType == ResourceType.DatabaseAccount) {
            return Paths.DATABASE_ACCOUNT_PATH_SEGMENT + "/" + ownerOrResourceId;
        } else if (resourceType == ResourceType.ClientEncryptionKey) {
            ResourceId clientEncryptionKeyId = ResourceId.parse(ownerOrResourceId);
            return Paths.DATABASES_PATH_SEGMENT + "/" + clientEncryptionKeyId.getDatabaseId().toString() + "/" +
                Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT + "/" + clientEncryptionKeyId.getClientEncryptionKeyId().toString();
        } else if (resourceType == ResourceType.PartitionKey && operationType == OperationType.Delete) {
            ResourceId documentCollectionId = ResourceId.parse(ownerOrResourceId);

            return Paths.DATABASES_PATH_SEGMENT + "/" + documentCollectionId.getDatabaseId().toString() + "/" +
                Paths.COLLECTIONS_PATH_SEGMENT + "/" + documentCollectionId.getDocumentCollectionId().toString() + "/" +
                Paths.OPERATIONS_PATH_SEGMENT + "/" + Paths.PARTITION_KEY_DELETE_PATH_SEGMENT;
        }

        String errorMessage = "invalid resource type";
        throw new IllegalStateException(errorMessage);
    }

    public static PathInfo parsePathSegments(String resourceUrl) {
        String[] segments = StringUtils.strip(resourceUrl, "/").split("/");
        if (segments == null || segments.length < 1) {
            return null;
        }

        int uriSegmentsCount = segments.length;
        String segmentOne = StringUtils.strip(segments[uriSegmentsCount - 1], "/");
        String segmentTwo = (uriSegmentsCount >= 2) ? StringUtils.strip(segments[uriSegmentsCount - 2], "/")
                : StringUtils.EMPTY;

        // handle name based operation
        if (uriSegmentsCount >= 2) {
            // parse the databaseId, if failed, it is name based routing
            // mediaId is special, we will treat it always as id based.
            if (Paths.MEDIA_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.OFFERS_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.PARTITIONS_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.DATABASE_ACCOUNT_PATH_SEGMENT.compareTo(segments[0]) != 0) {
                Pair<Boolean, ResourceId> result = ResourceId.tryParse(segments[1]);
                if (!result.getLeft() || !result.getRight().isDatabaseId()) {
                    return parseNameSegments(resourceUrl, segments);
                }
            }

        }

        // Feed paths have odd number of segments
        if ((uriSegmentsCount % 2 != 0) && isResourceType(segmentOne)) {
            return new PathInfo(true, segmentOne,
                    segmentOne.compareToIgnoreCase(Paths.DATABASES_PATH_SEGMENT) != 0 ? segmentTwo : StringUtils.EMPTY,
                    false);
        } else if (isResourceType(segmentTwo)) {
            return new PathInfo(false, segmentTwo, segmentOne, false);
        }

        return null;
    }

    /**
     * Method which will return boolean based on whether it is able to parse the
     * path and name segment from resource url , and fill info in PathInfo object
     * @param resourceUrl  Complete ResourceLink
     * @param pathInfo Path info object which will hold information
     * @param clientVersion The Client version
     * @return
     */
    public static boolean tryParsePathSegments(String resourceUrl, PathInfo pathInfo, String clientVersion) {
        pathInfo.resourcePath = StringUtils.EMPTY;
        pathInfo.resourceIdOrFullName = StringUtils.EMPTY;
        pathInfo.isFeed = false;
        pathInfo.isNameBased = false;
        if (StringUtils.isEmpty(resourceUrl)) {
            return false;
        }
        String trimmedStr = StringUtils.strip(resourceUrl, Constants.Properties.PATH_SEPARATOR);
        String[] segments = StringUtils.split(trimmedStr, Constants.Properties.PATH_SEPARATOR);
        if (segments == null || segments.length < 1) {
            return false;
        }
        int uriSegmentsCount = segments.length;
        String segmentOne = segments[uriSegmentsCount - 1];
        String segmentTwo = (uriSegmentsCount >= 2) ? segments[uriSegmentsCount - 2] : StringUtils.EMPTY;

        // handle name based operation
        if (uriSegmentsCount >= 2) {
            // parse the databaseId, if failed, it is name based routing
            // mediaId is special, we will treat it always as id based.
            if (Paths.MEDIA_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.OFFERS_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.PARTITIONS_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.DATABASE_ACCOUNT_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.TOPOLOGY_PATH_SEGMENT.compareTo(segments[0]) != 0
                    && Paths.RID_RANGE_PATH_SEGMENT.compareTo(segments[0]) != 0) {
                Pair<Boolean, ResourceId> result = ResourceId.tryParse(segments[1]);
                if (!result.getLeft() || !result.getRight().isDatabaseId()) {
                    pathInfo.isNameBased = true;
                    return tryParseNameSegments(resourceUrl, segments, pathInfo);
                }
            }
        }
            // Feed paths have odd number of segments
            if ((uriSegmentsCount % 2 != 0) && PathsHelper.isResourceType(segmentOne)) {
                pathInfo.isFeed = true;
                pathInfo.resourcePath = segmentOne;
                // The URL for dbs may contain the management endpoint as the segmentTwo which
                // should not be used as resourceId
                if (!segmentOne.equalsIgnoreCase(Paths.DATABASES_PATH_SEGMENT)) {
                    pathInfo.resourceIdOrFullName = segmentTwo;
                }
            } else if (PathsHelper.isResourceType(segmentTwo)) {
                pathInfo.isFeed = false;
                pathInfo.resourcePath = segmentTwo;
                pathInfo.resourceIdOrFullName = segmentOne;
                // Media ID is not supposed to be used for any ID verification. However, if the
                // old client makes a call for media ID
                // we still need to support it.
                // For new clients, parse to return the attachment id. For old clients do not
                // modify.
                if (!StringUtils.isEmpty(clientVersion)
                        && pathInfo.resourcePath.equalsIgnoreCase(Paths.MEDIA_PATH_SEGMENT)) {
                    String attachmentId = null;
                    byte storeIndex = 0;
                    // MEDIA Id parsing code  will come here , supported MediaIdHelper file missing in java sdk(Sync and Async both)
                    //Below code from .net
                    // if (!MediaIdHelper.TryParseMediaId(resourceIdOrFullName, out attachmentId, out storeIndex))
                    //  {
                    //    return false;
                    //}
                    //resourceIdOrFullName = attachmentId;
                }
            } else {
                return false;
            }

        return true;

    }

    /**
     * Method which will return boolean based on whether it is able to parse the
     * name segment from resource url , and fill info in PathInfo object
     * @param resourceUrl  Complete ResourceLink
     * @param segments
     * @param pathInfo Path info object which will hold information
     * @return
     */
    private static boolean tryParseNameSegments(String resourceUrl, String[] segments, PathInfo pathInfo) {
        pathInfo.isFeed = false;
        pathInfo.resourceIdOrFullName = "";
        pathInfo.resourcePath = "";
        if (segments == null || segments.length < 1) {
            return false;
        }
        if (segments.length % 2 == 0) {
            // even number, assume it is individual resource
            if (isResourceType(segments[segments.length - 2])) {
                pathInfo.resourcePath = segments[segments.length - 2];
                pathInfo.resourceIdOrFullName = unescapeJavaAndTrim(resourceUrl);
                return true;
            }
        } else {
                // odd number, assume it is feed request
                if (isResourceType(segments[segments.length - 1])) {
                    pathInfo.isFeed = true;
                    pathInfo.resourcePath = segments[segments.length - 1];
                    String resourceIdOrFullName = resourceUrl.substring(0, StringUtils.removeEnd(resourceUrl,Paths.ROOT).lastIndexOf(Paths.ROOT));
                    pathInfo.resourceIdOrFullName = unescapeJavaAndTrim(resourceIdOrFullName);
                    return true;
                }
        }
        return false;
    }

    public static PathInfo parseNameSegments(String resourceUrl, String[] segments) {
        if (segments == null || segments.length < 1) {
            return null;
        }

        if (segments.length % 2 == 0) {
            // even number, assume it is individual resource
            if (isResourceType(segments[segments.length - 2])) {
                return new PathInfo(false,
                                    segments[segments.length - 2],
                                    unescapeJavaAndTrim(resourceUrl),
                         true);
            }
        } else {
            // odd number, assume it is feed request
            if (isResourceType(segments[segments.length - 1])) {
                return new PathInfo(true,
                                    segments[segments.length - 1],
                                    unescapeJavaAndTrim(
                                        resourceUrl.substring(0,
                                                StringUtils.removeEnd(resourceUrl, Paths.ROOT).lastIndexOf(Paths.ROOT))),
                         true);
            }
        }

        return null;
    }

    public static String unescapeJavaAndTrim(String resourceUrl) {
        if (resourceUrl == null) {
            return null;
        }

        int startInclusiveIndex = 0;

        while (startInclusiveIndex < resourceUrl.length() && resourceUrl.charAt(startInclusiveIndex) == Paths.ROOT_CHAR) {
            startInclusiveIndex++;
        }

        if (startInclusiveIndex == resourceUrl.length()) {
            return "";
        }

        int endExclusiveIndex = resourceUrl.length();
        while (endExclusiveIndex > startInclusiveIndex && resourceUrl.charAt(endExclusiveIndex - 1) == Paths.ROOT_CHAR) {
            endExclusiveIndex--;
        }

        for (int startLoopIndex = startInclusiveIndex; startLoopIndex < endExclusiveIndex; startLoopIndex++) {
            if (resourceUrl.charAt(startLoopIndex)== Paths.ESCAPE_CHAR) {
                // Found an escape character lets run the StringEscapeUtils.unescapeJava
                return StringEscapeUtils.unescapeJava(StringUtils.strip(resourceUrl, Paths.ROOT));
            }
        }

        // No escape character found
        if (startInclusiveIndex == 0 && endExclusiveIndex == resourceUrl.length()) {
            return resourceUrl;
        }

        return resourceUrl.substring(startInclusiveIndex, endExclusiveIndex);
    }

    private static boolean isResourceType(String resourcePathSegment) {
        if (StringUtils.isEmpty(resourcePathSegment)) {
            return false;
        }

        switch (resourcePathSegment.toLowerCase(Locale.ROOT)) {
            case Paths.ATTACHMENTS_PATH_SEGMENT:
            case Paths.COLLECTIONS_PATH_SEGMENT:
            case Paths.DATABASES_PATH_SEGMENT:
            case Paths.PERMISSIONS_PATH_SEGMENT:
            case Paths.USERS_PATH_SEGMENT:
            case Paths.DOCUMENTS_PATH_SEGMENT:
            case Paths.STORED_PROCEDURES_PATH_SEGMENT:
            case Paths.TRIGGERS_PATH_SEGMENT:
            case Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT:
            case Paths.CONFLICTS_PATH_SEGMENT:
            case Paths.MEDIA_PATH_SEGMENT:
            case Paths.OFFERS_PATH_SEGMENT:
            case Paths.PARTITIONS_PATH_SEGMENT:
            case Paths.DATABASE_ACCOUNT_PATH_SEGMENT:
            case Paths.TOPOLOGY_PATH_SEGMENT:
            case Paths.PARTITION_KEY_RANGES_PATH_SEGMENT:
            case Paths.SCHEMAS_PATH_SEGMENT:
            case Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT:
                return true;
            default:
                return false;
        }
    }

    public static String generatePathForNameBased(ResourceType resourceType, String resourceOwnerFullName, String resourceName) {
        switch (resourceType) {
            case Database:
                return Paths.DATABASES_PATH_SEGMENT + "/" + resourceName;
            case DocumentCollection:
                return resourceOwnerFullName + "/" + Paths.COLLECTIONS_PATH_SEGMENT + "/" + resourceName;
            case StoredProcedure:
                return resourceOwnerFullName + "/" + Paths.STORED_PROCEDURES_PATH_SEGMENT + "/" + resourceName;
            case UserDefinedFunction:
                return resourceOwnerFullName + "/" + Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT + "/" + resourceName;
            case Trigger:
                return resourceOwnerFullName + "/" + Paths.TRIGGERS_PATH_SEGMENT + "/" + resourceName;
            case Attachment:
                return resourceOwnerFullName + "/" + Paths.ATTACHMENTS_PATH_SEGMENT + "/" + resourceName;
            case Conflict:
                return resourceOwnerFullName + "/" + Paths.CONFLICTS_PATH_SEGMENT + "/" + resourceName;
            case Document:
                return resourceOwnerFullName + "/" + Paths.DOCUMENTS_PATH_SEGMENT + "/" + resourceName;
            case Offer:
                return resourceOwnerFullName + "/" + Paths.OFFERS_PATH_SEGMENT + "/" + resourceName;
            case Permission:
                return resourceOwnerFullName + "/" + Paths.PERMISSIONS_PATH_SEGMENT + "/" + resourceName;
            case User:
                return resourceOwnerFullName + "/" + Paths.USERS_PATH_SEGMENT + "/" + resourceName;
            case PartitionKeyRange:
                return resourceOwnerFullName + "/" + Paths.PARTITION_KEY_RANGES_PATH_SEGMENT + "/" + resourceName;
            default:
                return null;
        }
    }

    public static String getCollectionPath(String resourceFullName) {
        if (resourceFullName != null) {
            String trimmedResourceFullName = Utils.trimBeginningAndEndingSlashes(resourceFullName);
            int index = indexOfNth(trimmedResourceFullName, '/', 4);
            if (index > 0)
                return trimmedResourceFullName.substring(0, index);
        }

        return resourceFullName;
    }

    public static String getDatabasePath(String resourceFullName) {
        if (resourceFullName != null) {
            int index = indexOfNth(resourceFullName, '/', 2);
            if (index > 0)
                return resourceFullName.substring(0, index);
        }

        return resourceFullName;
    }

    public static String getParentByIndex(String resourceFullName, int segmentIndex) {
        int index = indexOfNth(resourceFullName, '/', segmentIndex);
        if (index > 0)
            return resourceFullName.substring(0, index);
        else {
            index = indexOfNth(resourceFullName, '/', segmentIndex - 1);
            if (index > 0)
                return resourceFullName;
            else
                return null;
        }
    }
    public static boolean isNameBased(String resourceIdOrFullName) {
        // quick way to tell whether it is resourceId nor not, non conclusively.
        if (resourceIdOrFullName != null && !resourceIdOrFullName.isEmpty()
                && resourceIdOrFullName.length() > 4 && resourceIdOrFullName.charAt(3) == '/') {
            return true;
        }
        return false;
    }

    private static int indexOfNth(String str, char value, int nthOccurance) {
        int remaining = nthOccurance;
        char[] characters = str.toCharArray();
        for (int i = 0; i < characters.length; i++) {
            if (characters[i] == value) {
                remaining--;
                if (remaining == 0) {
                    return i;
                }
            }
        }
        return -1;
    }

    public static ResourceType getResourcePathSegment(String resourcePathSegment) throws BadRequestException {
        if (StringUtils.isEmpty(resourcePathSegment)) {
            String message = String.format(RMResources.StringArgumentNullOrEmpty, "resourcePathSegment");
            throw new BadRequestException(message);
        }

        switch (resourcePathSegment) {
            case Paths.ATTACHMENTS_PATH_SEGMENT:
                return ResourceType.Attachment;

            case Paths.COLLECTIONS_PATH_SEGMENT:
                return ResourceType.DocumentCollection;

            case Paths.DATABASES_PATH_SEGMENT:
                return ResourceType.Database;

            case Paths.PERMISSIONS_PATH_SEGMENT:
                return ResourceType.Permission;

            case Paths.USERS_PATH_SEGMENT:
                return ResourceType.User;

            case Paths.DOCUMENTS_PATH_SEGMENT:
                return ResourceType.Document;

            case Paths.STORED_PROCEDURES_PATH_SEGMENT:
                return ResourceType.StoredProcedure;

            case Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT:
                return ResourceType.UserDefinedFunction;

            case Paths.TRIGGERS_PATH_SEGMENT:
                return ResourceType.Trigger;

            case Paths.CONFLICTS_PATH_SEGMENT:
                return ResourceType.Conflict;

            case Paths.OFFERS_PATH_SEGMENT:
                return ResourceType.Offer;

            case Paths.SCHEMAS_PATH_SEGMENT:
                return ResourceType.Schema;
        }

        String errorMessage = String.format(RMResources.UnknownResourceType, resourcePathSegment);
        throw new BadRequestException(errorMessage);
    }

    public static String getResourcePath(ResourceType resourceType) throws BadRequestException {
        switch (resourceType) {
            case Database:
                return Paths.DATABASES_PATH_SEGMENT;

            case DocumentCollection:
                return Paths.COLLECTIONS_PATH_SEGMENT;

            case Document:
                return Paths.DOCUMENTS_PATH_SEGMENT;

            case StoredProcedure:
                return Paths.STORED_PROCEDURES_PATH_SEGMENT;

            case UserDefinedFunction:
                return Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT;

            case Trigger:
                return Paths.TRIGGERS_PATH_SEGMENT;

            case Conflict:
                return Paths.CONFLICTS_PATH_SEGMENT;

            case Attachment:
                return Paths.ATTACHMENTS_PATH_SEGMENT;

            case User:
                return Paths.USERS_PATH_SEGMENT;

            case Permission:
                return Paths.PERMISSIONS_PATH_SEGMENT;

            case Offer:
                return Paths.OFFERS_PATH_SEGMENT;

            case MasterPartition:
            case ServerPartition:
                return Paths.PARTITIONS_PATH_SEGMENT;

            case PartitionKeyRange:
                return Paths.PARTITION_KEY_RANGES_PATH_SEGMENT;

            case Media:
                return Paths.MEDIA_ROOT;

            case Schema:
                return Paths.SCHEMAS_PATH_SEGMENT;


            case DatabaseAccount:
            case Topology:

                return Paths.ROOT;

            default:
                String errorMessage = String.format(RMResources.UnknownResourceType, resourceType.toString());
                throw new BadRequestException(errorMessage);
        }
    }

    public static boolean validateResourceFullName(ResourceType resourceType, String resourceFullName) {
        String[] segments = StringUtils.split(resourceFullName, '/');
        String[] resourcePathArray = getResourcePathArray(resourceType);
        if (resourcePathArray == null) {
            return false;
        }

        if (segments.length != resourcePathArray.length * 2) {
            return false;
        }
        for (int i = 0; i < resourcePathArray.length; i++) {
            if(resourcePathArray[i].compareTo(segments[2 * i]) != 0) {
                return false;
            }
        }
        return true;
    }

    private static String[] getResourcePathArray(ResourceType resourceType) {
        List<String> segments = new ArrayList<String>();
        segments.add(Paths.DATABASES_PATH_SEGMENT);

        if (resourceType == ResourceType.Permission ||
            resourceType == ResourceType.User) {
            segments.add(Paths.USERS_PATH_SEGMENT);
            if (resourceType == ResourceType.Permission) {
                segments.add(Paths.PERMISSIONS_PATH_SEGMENT);
            }
        } else if (resourceType == ResourceType.ClientEncryptionKey) {
            segments.add(Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT);
        } else if (resourceType == ResourceType.DocumentCollection ||
                    resourceType == ResourceType.StoredProcedure ||
                    resourceType == ResourceType.UserDefinedFunction ||
                    resourceType == ResourceType.Trigger ||
                    resourceType == ResourceType.Conflict ||
                    resourceType == ResourceType.Attachment ||
                    resourceType == ResourceType.Document ||
                    resourceType == ResourceType.PartitionKeyRange ||
                    resourceType == ResourceType.Schema) {
            segments.add(Paths.COLLECTIONS_PATH_SEGMENT);
            if (resourceType == ResourceType.StoredProcedure) {
                segments.add(Paths.STORED_PROCEDURES_PATH_SEGMENT);
            } else if(resourceType == ResourceType.UserDefinedFunction) {
                segments.add(Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT);
            } else if(resourceType == ResourceType.Trigger) {
                segments.add(Paths.TRIGGERS_PATH_SEGMENT);
            } else if (resourceType == ResourceType.Conflict) {
                segments.add(Paths.CONFLICTS_PATH_SEGMENT);
            } else if (resourceType == ResourceType.Schema) {
                segments.add(Paths.SCHEMAS_PATH_SEGMENT);
            } else if(resourceType == ResourceType.Document ||
                      resourceType == ResourceType.Attachment) {
                segments.add(Paths.DOCUMENTS_PATH_SEGMENT);
                if (resourceType == ResourceType.Attachment) {
                    segments.add(Paths.ATTACHMENTS_PATH_SEGMENT);
                }
            } else if(resourceType == ResourceType.PartitionKeyRange) {
                segments.add(Paths.PARTITION_KEY_RANGES_PATH_SEGMENT);
            } else if (resourceType == ResourceType.PartitionKey) {
                segments.add(Paths.COLLECTIONS_PATH_SEGMENT);
                segments.add(Paths.OPERATIONS_PATH_SEGMENT);
            }
        } else if (resourceType != ResourceType.Database) {
            return null;
        }
        return segments.stream().toArray(String[]::new);
    }

    public static boolean validateResourceId(ResourceType resourceType, String resourceId) {
        if (resourceType == ResourceType.Conflict) {
            return PathsHelper.validateConflictId(resourceId);
        } else if (resourceType == ResourceType.Database) {
            return PathsHelper.validateDatabaseId(resourceId);
        } else if (resourceType == ResourceType.DocumentCollection) {
            return PathsHelper.validateDocumentCollectionId(resourceId);
        } else if (resourceType == ResourceType.Document) {
            return PathsHelper.validateDocumentId(resourceId);
        } else if (resourceType == ResourceType.Permission) {
            return PathsHelper.validatePermissionId(resourceId);
        } else if (resourceType == ResourceType.StoredProcedure) {
            return PathsHelper.validateStoredProcedureId(resourceId);
        } else if (resourceType == ResourceType.Trigger) {
            return PathsHelper.validateTriggerId(resourceId);
        } else if (resourceType == ResourceType.UserDefinedFunction) {
            return PathsHelper.validateUserDefinedFunctionId(resourceId);
        } else if (resourceType == ResourceType.User) {
            return PathsHelper.validateUserId(resourceId);
        } else if (resourceType == ResourceType.Attachment) {
            return PathsHelper.validateAttachmentId(resourceId);
        } else if (resourceType == ResourceType.ClientEncryptionKey) {
            return PathsHelper.validateClientEncryptionKeyId(resourceId);
        }else {
            logger.error(String.format("ValidateResourceId not implemented for Type %s in ResourceRequestHandler", resourceType.toString()));
            return false;
        }
    }

    public static boolean validateDatabaseId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getDatabase() != 0;
    }

    public static boolean validateDocumentCollectionId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getDocumentCollection() != 0;
    }

    public static boolean validateDocumentId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getDocument() != 0;
    }

    public static boolean validateConflictId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getConflict() != 0;
    }

    public static boolean validateAttachmentId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getAttachment() != 0;
    }

    public static boolean validatePermissionId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getPermission() != 0;
    }

    public static boolean validateStoredProcedureId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getStoredProcedure() != 0;
    }

    public static boolean validateTriggerId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getTrigger() != 0;
    }

    public static boolean validateUserDefinedFunctionId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getUserDefinedFunction() != 0;
    }

    public static boolean validateUserId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getUser() != 0;
    }

    public static boolean validateClientEncryptionKeyId(String resourceIdString) {
        Pair<Boolean, ResourceId> pair = ResourceId.tryParse(resourceIdString);
        return pair.getLeft() && pair.getRight().getClientEncryptionKey() != 0;
    }

    public static boolean isPublicResource(Resource resourceType) {
        if (resourceType instanceof Database ||
                resourceType instanceof DocumentCollection ||
                resourceType instanceof StoredProcedure ||
                resourceType instanceof UserDefinedFunction ||
                resourceType instanceof Trigger ||
                resourceType instanceof Conflict ||
                resourceType instanceof User ||
                resourceType instanceof Permission ||
                resourceType instanceof Document ||
                resourceType instanceof Offer
        ) {
            return true;
        } else {
            return false;
        }
    }

}