Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.microsoft.azure.documentdb.internal.SessionContainer Maven / Gradle / Ivy
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
*/
package com.microsoft.azure.documentdb.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.DocumentCollection;
import com.microsoft.azure.documentdb.Error;
import com.microsoft.azure.documentdb.PartitionKeyRange;
import com.microsoft.azure.documentdb.internal.directconnectivity.GatewayAddressCache;
import com.microsoft.azure.documentdb.internal.routing.ClientCollectionCache;
import com.microsoft.azure.documentdb.internal.routing.PartitionKeyRangeCache;
/**
* Used internally to cache the collections' session tokens in the Azure Cosmos DB database service.
*/
public final class SessionContainer {
private static final String EMPTY_SESSION_TOKEN = "";
private static final char SESSION_TOKEN_SEPARATOR = ',';
private static final char SESSION_TOKEN_PARTITION_SPLITTER = ':';
private static final Logger logger = LoggerFactory.getLogger(SessionContainer.class);
private ClientCollectionCache collectionCache;
private PartitionKeyRangeCache partitionKeyRangeCache;
/**
* Session token cache that maps collection ResourceID to session tokens
*/
private final ConcurrentHashMap> collectionResourceIdToSessionTokens;
/**
* Collection ResourceID cache that maps collection name to collection ResourceID
* When collection name is provided instead of self-link, this is used in combination with
* collectionResourceIdToSessionTokens to retrieve the session token for the collection by name
*/
private final ConcurrentHashMap collectionNameToCollectionResourceId;
private final String hostName;
public SessionContainer(String hostName,
ClientCollectionCache collectionCache,
PartitionKeyRangeCache partitionKeyRangeCache) {
this(hostName,
collectionCache,
partitionKeyRangeCache,
new ConcurrentHashMap(),
new ConcurrentHashMap>());
}
public SessionContainer(String hostName,
ClientCollectionCache collectionCache,
PartitionKeyRangeCache partitionKeyRangeCache,
ConcurrentHashMap nameToRidMap,
ConcurrentHashMap> ridToTokensMap) {
this.hostName = hostName;
this.collectionCache = collectionCache;
this.partitionKeyRangeCache = partitionKeyRangeCache;
this.collectionResourceIdToSessionTokens = ridToTokensMap;
this.collectionNameToCollectionResourceId = nameToRidMap;
}
public String getHostName() {
return this.hostName;
}
private ConcurrentHashMap getPartitionKeyRangeIdToTokenMap(DocumentServiceRequest request) {
return getPartitionKeyRangeIdToTokenMap(request.getIsNameBased(), request.getResourceId(), request.getResourceAddress());
}
private ConcurrentHashMap getPartitionKeyRangeIdToTokenMap(boolean isNameBased, String rId, String resourceAddress) {
ConcurrentHashMap rangeIdToTokenMap = null;
if (!isNameBased) {
if (!StringUtils.isEmpty(rId)) {
ResourceId resourceId = ResourceId.parse(rId);
if (resourceId.getDocumentCollection() != 0) {
rangeIdToTokenMap =
this.collectionResourceIdToSessionTokens.get(resourceId.getUniqueDocumentCollectionId());
}
}
} else {
String collectionName = Utils.getCollectionName(resourceAddress);
if (!StringUtils.isEmpty(collectionName) && this.collectionNameToCollectionResourceId.containsKey(collectionName)) {
rangeIdToTokenMap = this.collectionResourceIdToSessionTokens.get(
this.collectionNameToCollectionResourceId.get(collectionName));
}
}
return rangeIdToTokenMap;
}
/**
* Resolves a session token for request. This should be invoked for read only requests which require a session token in session consistency.
*
* It attempts to resolve the target partition key range.
* 1) If partition key range is resolved: it will find the local session token for the given partition or if not found uses parent partition session token.
* 2) If partition key range is not resolved: it passes the global session token.
*
* @param request
* @return
* @throws DocumentClientException
*/
public String resolveSessionToken(DocumentServiceRequest request) throws DocumentClientException {
if (request == null) {
throw new IllegalArgumentException("request cannot be null");
}
String userSessionToken = request.getHeaders().get(HttpConstants.HttpHeaders.SESSION_TOKEN);
PartitionKeyRange partitionKeyRange = null;
if (request.getResourceType().isPartitioned()) {
partitionKeyRange = resolvePartitionKeyRange(request, false);
}
if (!StringUtils.isEmpty(userSessionToken)) {
return parseLocalSessionToken(userSessionToken, partitionKeyRange);
}
return resolveSessionToken(request.getIsNameBased(), request.getResourceId(), request.getResourceAddress(),
partitionKeyRange);
}
/**
* Parses combinedSessionToken and returns local session token scoped to the given partition or one of its parents.
*
* @param combinedSessionToken
* @param partitionKeyRange
* @return
*/
private static String parseLocalSessionToken(String combinedSessionToken, PartitionKeyRange partitionKeyRange) {
if (combinedSessionToken == null) {
return null;
}
if (partitionKeyRange == null) {
return combinedSessionToken;
}
String[] localSessionTokens = StringUtils.split(combinedSessionToken, SESSION_TOKEN_SEPARATOR);
Map partitionToLSN = new HashMap<>();
for(String localSessionToken: localSessionTokens) {
String[] parts = StringUtils.split(localSessionToken, SESSION_TOKEN_PARTITION_SPLITTER);
if (parts.length == 2) {
partitionToLSN.put(parts[0], parts[1]);
}
}
return findLocalSessionToken(partitionToLSN, partitionKeyRange);
}
/**
* given the partition to lsn map and partition key range, it finds the token associated with the partition if not found returns the parent partition session.
*
* If no session associated to partition or its parents found, returns empty session token.
*
* @param partitionToLSN
* @param partitionKeyRange
* @return
*/
private static String findLocalSessionToken(Map partitionToLSN, PartitionKeyRange partitionKeyRange) {
if (partitionKeyRange == null) {
throw new IllegalArgumentException("partitionKeyRange is null");
}
// if local session token for the partition is found use it
if (partitionToLSN.containsKey(partitionKeyRange.getId())) {
return partitionKeyRange.getId() + SESSION_TOKEN_PARTITION_SPLITTER + getSessionTokenString(partitionToLSN.get(partitionKeyRange.getId()));
}
// if the local session token for any of parents is found use it.
Collection parents = partitionKeyRange.getParents();
List parentList = new ArrayList<>(parents);
for (int i = parentList.size() -1; i >= 0; i--) {
String parentId = parentList.get(i);
if (partitionToLSN.containsKey(parentId)) {
return parentId + SESSION_TOKEN_PARTITION_SPLITTER + getSessionTokenString(partitionToLSN.get(parentId));
}
}
return EMPTY_SESSION_TOKEN;
}
private static String getSessionTokenString(Object sessionToken) {
if (sessionToken instanceof VectorSessionToken) {
return ((VectorSessionToken) sessionToken).convertToString();
} else {
return (String) sessionToken;
}
}
/**
* Resolves the partition key range
*
* @param request
* @param refreshCache
* @return
* @throws DocumentClientException
*/
private PartitionKeyRange resolvePartitionKeyRange(AbstractDocumentServiceRequest request, boolean refreshCache) throws DocumentClientException {
if (refreshCache) {
request.setForceAddressRefresh(true);
request.setForceNameCacheRefresh(true);
}
PartitionKeyRange partitionKeyRange = null;
DocumentCollection collection = this.collectionCache.resolveCollection(request);
String partitionKeyRangeId = null;
if (request.getHeaders().get(HttpConstants.HttpHeaders.PARTITION_KEY) != null) {
partitionKeyRange = GatewayAddressCache.tryResolveServerPartitionByPartitionKey(this.partitionKeyRangeCache,
request.getHeaders().get(HttpConstants.HttpHeaders.PARTITION_KEY),
collection,
request.isForcePartitionKeyRangeRefresh());
if(partitionKeyRange != null)
{
partitionKeyRangeId = partitionKeyRange.getId();
}
} else if (request.getPartitionKeyRangeIdentity() != null) {
partitionKeyRangeId = request.getPartitionKeyRangeIdentity().getPartitionKeyRangeId();
partitionKeyRange = partitionKeyRangeCache.getPartitionKeyRangeById(
collection.getSelfLink(), partitionKeyRangeId, request.isForcePartitionKeyRangeRefresh());
}
if (partitionKeyRangeId == null) {
return null;
}
logger.debug("request.isForcePartitionKeyRangeRefresh={}, partitionKeyRange={}", request.isForcePartitionKeyRangeRefresh(), partitionKeyRange);
if (partitionKeyRange == null) {
if (refreshCache) {
// we already refreshed cache but still couldn't resolve partition key range
Map responseHeaders = new HashMap();
responseHeaders.put(HttpConstants.HttpHeaders.SUB_STATUS, String.valueOf(HttpConstants.SubStatusCodes.PARTITION_KEY_RANGE_GONE));
logger.error("Invalid Partition Key Range");
throw new DocumentClientException(HttpConstants.StatusCodes.GONE,
new Error("{ 'message': 'Invalid partition key range' }"), responseHeaders);
}
// need to refresh cache, maybe split happened
return resolvePartitionKeyRange(request, true);
} else {
request.setResolvedPartitionKeyRange(partitionKeyRange);
return partitionKeyRange;
}
}
private String resolveSessionToken(boolean isNameBased, String rId, String resourceAddress, PartitionKeyRange partitionKeyRange) {
ConcurrentHashMap rangeIdToTokenMap = this.getPartitionKeyRangeIdToTokenMap(isNameBased, rId, resourceAddress);
if (rangeIdToTokenMap == null) {
// we don't know about this collection.
return EMPTY_SESSION_TOKEN;
}
if (partitionKeyRange == null) {
// there is no information for target partition key range id
// using global session token
return getCombinedSessionToken(rangeIdToTokenMap);
}
// getting the partition session token
VectorSessionToken token = rangeIdToTokenMap.get(partitionKeyRange.getId());
if (token != null) {
return partitionKeyRange.getId() + SESSION_TOKEN_PARTITION_SPLITTER + token.convertToString();
}
// the partition session token is not cached, using the parents if any
Collection parentPartitions = partitionKeyRange.getParents();
if (parentPartitions == null || parentPartitions.isEmpty()) {
// if no parents then return empty session token
return EMPTY_SESSION_TOKEN;
}
return findLocalSessionToken(rangeIdToTokenMap, partitionKeyRange);
}
public String resolveGlobalSessionToken(String collectionLink) {
if (StringUtils.isEmpty(collectionLink)) {
throw new IllegalArgumentException("collectionLink cannot be null");
}
PathInfo pathInfo = PathsHelper.parsePathSegments(collectionLink);
if (pathInfo == null) {
return EMPTY_SESSION_TOKEN;
}
return this.resolveSessionToken(pathInfo.isNameBased, pathInfo.resourceIdOrFullName, pathInfo.resourcePath, null);
}
public void clearToken(final DocumentServiceRequest request) {
Long collectionResourceId = null;
if (!request.getIsNameBased()) {
if (!StringUtils.isEmpty(request.getResourceId())) {
ResourceId resourceId = ResourceId.parse(request.getResourceId());
if (resourceId.getDocumentCollection() != 0) {
collectionResourceId = resourceId.getUniqueDocumentCollectionId();
}
}
} else {
String collectionName = Utils.getCollectionName(request.getResourceAddress());
if (!StringUtils.isEmpty(collectionName)) {
collectionResourceId = this.collectionNameToCollectionResourceId.get(collectionName);
this.collectionNameToCollectionResourceId.remove(collectionName);
}
}
if (collectionResourceId != null) {
this.collectionResourceIdToSessionTokens.remove(collectionResourceId);
}
}
/**
* Updates the session cache with the new session token from response.
*
* Also if partition key range cache is stale, it refreshes the partition key range cache
*
* @param request
* @param responseHeaders
* @throws DocumentClientException
*/
public void setSessionToken(AbstractDocumentServiceRequest request, Map responseHeaders) throws DocumentClientException {
if (responseHeaders != null && !request.isReadingFromMaster()) {
String sessionToken = responseHeaders.get(HttpConstants.HttpHeaders.SESSION_TOKEN);
if (!StringUtils.isEmpty(sessionToken)) {
if (request.getResourceType().isPartitioned()) {
String requestSessionToken = request.getHeaders().get(HttpConstants.HttpHeaders.SESSION_TOKEN);
String[] requestTokenParts = requestSessionToken == null ? null : StringUtils.split(requestSessionToken, SESSION_TOKEN_PARTITION_SPLITTER);
String[] responseTokenParts = StringUtils.split(sessionToken, SESSION_TOKEN_PARTITION_SPLITTER);
// check if split happened and partition key range require refresh
if (responseTokenParts.length == 2) {
if (StringUtils.isEmpty(requestSessionToken)
|| !StringUtils.equals(requestTokenParts[0], responseTokenParts[0])) {
DocumentCollection collection = this.collectionCache.resolveCollection(request);
PartitionKeyRange partitionKeyRangeCache =
this.partitionKeyRangeCache.getPartitionKeyRangeById(collection.getSelfLink(), responseTokenParts[0], false);
if (partitionKeyRangeCache == null) {
// partition key range cache is stale refresh the cache.
this.partitionKeyRangeCache.getPartitionKeyRangeById(collection.getSelfLink(), responseTokenParts[0], true);
}
}
}
}
}
String ownerFullName = responseHeaders.get(HttpConstants.HttpHeaders.OWNER_FULL_NAME);
if (StringUtils.isEmpty(ownerFullName)) ownerFullName = request.getResourceAddress();
String collectionName = Utils.getCollectionName(ownerFullName);
String ownerId;
if (!request.getIsNameBased()) {
ownerId = request.getResourceId();
} else {
ownerId = responseHeaders.get(HttpConstants.HttpHeaders.OWNER_ID);
if (StringUtils.isEmpty(ownerId)) ownerId = request.getResourceId();
}
if (!StringUtils.isEmpty(ownerId)) {
ResourceId resourceId = ResourceId.parse(ownerId);
if (resourceId.getDocumentCollection() != 0 && !StringUtils.isEmpty(collectionName)) {
Long uniqueDocumentCollectionId = resourceId.getUniqueDocumentCollectionId();
this.setSessionToken(uniqueDocumentCollectionId, collectionName, sessionToken);
}
}
}
}
private void setSessionToken(long collectionRid, String collectionName, String sessionToken) throws DocumentClientException {
logger.trace("Set session token: collectionRid = {}, collectionName = {}, sessionToken = {}",
collectionRid, collectionName, sessionToken);
this.collectionResourceIdToSessionTokens.putIfAbsent(collectionRid,
new ConcurrentHashMap());
this.compareAndSetToken(sessionToken, this.collectionResourceIdToSessionTokens.get(collectionRid));
this.collectionNameToCollectionResourceId.putIfAbsent(collectionName, collectionRid);
}
private String getCombinedSessionToken(ConcurrentHashMap tokens) {
StringBuilder result = new StringBuilder();
if (tokens != null) {
for (Iterator> iterator = tokens.entrySet().iterator(); iterator.hasNext(); ) {
Entry entry = iterator.next();
result = result.append(entry.getKey()).append(SESSION_TOKEN_PARTITION_SPLITTER).append(entry.getValue().convertToString());
if (iterator.hasNext()) {
result = result.append(SESSION_TOKEN_SEPARATOR);
}
}
}
return result.toString();
}
private void compareAndSetToken(String newToken, ConcurrentHashMap oldTokens) throws DocumentClientException {
if (StringUtils.isNotEmpty(newToken)) {
String[] newTokenParts = StringUtils.split(newToken, SESSION_TOKEN_PARTITION_SPLITTER);
if (newTokenParts.length == 2) {
String range = newTokenParts[0];
VectorSessionToken newSessionToken = SessionTokenHelper.parse(newTokenParts[1]);
boolean success;
do {
VectorSessionToken oldSessionToken = oldTokens.putIfAbsent(range, newSessionToken);
// If there exists no previous session token, we're done.
success = (oldSessionToken == null);
if (!success) {
// Replace previous session token with merge of previous and current session tokens.
success = oldTokens.replace(range, oldSessionToken, oldSessionToken.merge(newSessionToken));
}
} while (!success);
}
}
}
VectorSessionToken resolvePartitionLocalSessionToken(DocumentServiceRequest request, String partitionKeyRangeId) {
return SessionTokenHelper.resolvePartitionLocalSessionToken(request, partitionKeyRangeId,
this.getPartitionKeyRangeIdToTokenMap(request));
}
}