com.microsoft.azure.documentdb.internal.AbstractDocumentServiceRequest Maven / Gradle / Ivy
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
*/
package com.microsoft.azure.documentdb.internal;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.azure.documentdb.ConsistencyLevel;
import com.microsoft.azure.documentdb.PartitionKeyRange;
import com.microsoft.azure.documentdb.internal.directconnectivity.StoreReadResult;
import com.microsoft.azure.documentdb.internal.directconnectivity.StoreResponse;
import com.microsoft.azure.documentdb.internal.routing.PartitionKeyRangeIdentity;
import com.microsoft.azure.documentdb.ClientSideRequestStatistics;
/**
* This is core Transport/Connection agnostic request to the Azure Cosmos DB database service.
*/
public class AbstractDocumentServiceRequest {
private final String resourceId;
private final ResourceType resourceType;
private final String path;
private final Map headers;
private volatile String continuation;
private boolean isMedia = false;
private final boolean isNameBased;
private final OperationType operationType;
private final String resourceAddress;
private volatile boolean forceNameCacheRefresh;
private volatile boolean forceAddressRefresh;
private volatile boolean forcePartitionKeyRangeRefresh;
private volatile VectorSessionToken sessionToken;
private volatile URI endpointOverride = null;
private final String activityId;
private volatile RequestChargeTracker requestChargeTracker;
private volatile String resourceFullName;
private volatile StoreReadResult quorumSelectedStoreResponse;
private volatile long quorumSelectedLSN;
private volatile long globalCommittedSelectedLSN;
private volatile StoreResponse globalStrongWriteResponse;
private volatile ConsistencyLevel originalRequestConsistencyLevel;
private volatile String originalSessionToken;
private volatile String resolvedCollectionRid;
private volatile PartitionKeyRangeIdentity partitionKeyRangeIdentity;
private volatile PartitionKeyRange resolvedPartitionKeyRange;
private volatile Integer defaultReplicaIndex;
private Boolean usePreferredLocations;
private Integer locationIndexToRoute;
private URI locationEndpointToRoute;
private boolean shouldClearSessionTokenOnSessionReadFailure;
private ClientSideRequestStatistics clientSideRequestStatistics;
private String httpMethod;
private boolean isUserProvidedPartitionKeyRangeIdentity;
/**
* Creates a AbstractDocumentServiceRequest
*
* @param operationType the operation type.
* @param resourceId the resource id.
* @param resourceType the resource type.
* @param path the path.
* @param headers the headers
*/
protected AbstractDocumentServiceRequest(OperationType operationType,
String resourceId,
ResourceType resourceType,
String path,
Map headers) {
this.operationType = operationType;
this.resourceType = resourceType;
this.path = path;
this.sessionToken = null;
this.headers = headers != null ? headers : new HashMap();
this.isNameBased = Utils.isNameBased(path);
this.activityId = Utils.getTimeBasedRandomUUID().toString();
if (!this.isNameBased) {
if (resourceType == ResourceType.Media) {
this.resourceId = getAttachmentIdFromMediaId(resourceId);
} else {
this.resourceId = resourceId;
}
this.resourceAddress = resourceId;
} else {
this.resourceAddress = this.path;
this.resourceId = null;
}
}
protected static String extractIdFromUri(String str)
{
final char separatorChar = '/';
int len = str.length();
if (len != 0 && str.charAt(len - 1) == separatorChar) {
--len;
}
if (len == 0) {
return "";
}
int ridStartIndex = 1, ridEndIndex = 0; // ridEndIndex will be one out
boolean isResourceType = true;
boolean resourceIdFound = false;
for (int i = 1; i < len; i++) {
if (str.charAt(i) == separatorChar) {
resourceIdFound = true;
isResourceType = !isResourceType;
if (!isResourceType) {
ridStartIndex = i + 1;
} else {
ridEndIndex = i;
}
}
}
if (! resourceIdFound) {
// case like /dbs
return "";
}
if (ridEndIndex <= ridStartIndex) {
ridEndIndex = len;
}
return str.substring(ridStartIndex, ridEndIndex);
}
static String getAttachmentIdFromMediaId(String mediaId) {
// '/' was replaced with '-'.
byte[] buffer = Base64.decodeBase64(mediaId.replace('-', '/').getBytes());
final int resoureIdLength = 20;
String attachmentId;
if (buffer.length > resoureIdLength) {
// We are cuting off the storage index.
byte[] newBuffer = new byte[resoureIdLength];
System.arraycopy(buffer, 0, newBuffer, 0, resoureIdLength);
attachmentId = Utils.encodeBase64String(newBuffer).replace('/', '-');
} else {
attachmentId = mediaId;
}
return attachmentId;
}
/**
* Gets the resource id.
*
* @return the resource id.
*/
public String getResourceId() {
return this.resourceId;
}
/**
* Gets the resource type.
*
* @return the resource type.
*/
public ResourceType getResourceType() {
return this.resourceType;
}
/**
* Gets the path.
*
* @return the path.
*/
public String getPath() {
return this.path;
}
/**
* Gets the request headers.
*
* @return the request headers.
*/
public Map getHeaders() {
return this.headers;
}
/**
* Gets the continuation.
*
* @return the continuation.
*/
public String getContinuation() {
return this.continuation;
}
public void setContinuation(String continuation) {
this.continuation = continuation;
}
public boolean getIsMedia() {
return this.isMedia;
}
public void setIsMedia(boolean isMedia) {
this.isMedia = isMedia;
}
public boolean getIsNameBased() {
return this.isNameBased;
}
public OperationType getOperationType() {
return this.operationType;
}
public String getResourceAddress() {
return resourceAddress;
}
/**
* Check whether the name caches should be refreshed or not. This is used to trigger named cached refreshes
* according to certain response from the backend. Name caches include collection cache, and partition key range
* routing map.
*
* @return a boolean value indicating whether the name caches should be refreshed during the request
*/
public boolean isForceNameCacheRefresh() {
return this.forceNameCacheRefresh;
}
/**
* Sets the value whether the name caches should be refreshed or not. This flag is used to trigger named cached
* refreshes according to certain response from the backend. Name caches include the collection cache.
*
* @param forceNameCacheRefresh a boolean value indicating whether the name caches should be refreshed during the
* request
*/
public void setForceNameCacheRefresh(boolean forceNameCacheRefresh) {
this.forceNameCacheRefresh = forceNameCacheRefresh;
}
/**
* Gets whether the address cache should be refreshed or not. This flag is used to trigger the address caches which
* include read, write, and alternate write address cache.
*
* @return a boolean indicating if the address caches should be refreshed or not
*/
public boolean isForceAddressRefresh() {
return this.forceAddressRefresh;
}
/**
* Sets whether the address cache should be refreshed or not. This flag is used to trigger the address caches which
* include read, write, and alternate write address cache.
*
* @param forceAddressRefresh a boolean indicating if the address caches should be refreshed or not
*/
public void setForceAddressRefresh(boolean forceAddressRefresh) {
this.forceAddressRefresh = forceAddressRefresh;
}
/**
* Sets whether the partitionKeyRange cache should be refreshed or not. This flag is used to trigger the partitionKeyRange caches which
* include read, write, and alternate write partitionKeyRange cache.
*
* @param forcePartitionKeyRangeRefresh a boolean indicating if the partitionKeyRange caches should be refreshed or not
*/
public void setForcePartitionKeyRangeRefresh(boolean forcePartitionKeyRangeRefresh) {
this.forcePartitionKeyRangeRefresh = forcePartitionKeyRangeRefresh;
}
/**
* Gets whether the partitionKeyRange cache should be refreshed or not. This flag is used to trigger the partitionKeyRange caches which
* include read, write, and alternate write partitionKeyRange cache.
*
* @return a boolean indicating if the partitionKeyRange caches should be refreshed or not
*/
public boolean isForcePartitionKeyRangeRefresh() {
return this.forcePartitionKeyRangeRefresh;
}
public VectorSessionToken getSessionToken() {
return this.sessionToken;
}
public void setSessionToken(VectorSessionToken sessionToken) {
this.sessionToken = sessionToken;
}
public URI getEndpointOverride() {
return this.endpointOverride;
}
public void setEndpointOverride(URI endpointOverride) {
this.endpointOverride = endpointOverride;
}
public String getActivityId() {
return this.activityId;
}
public RequestChargeTracker getRequestChargeTracker() {
return requestChargeTracker;
}
public void setRequestChargeTracker(RequestChargeTracker requestChargeTracker) {
this.requestChargeTracker = requestChargeTracker;
}
public String getResourceFullName() {
if (this.isNameBased) {
String trimmedPath = Utils.trimBeginingAndEndingSlashes(this.path);
String[] segments = StringUtils.split(trimmedPath, '/');
if (segments.length % 2 == 0) {
// if path has even segments, it is the individual resource
// like dbs/db1/colls/coll1
if (Utils.IsResourceType(segments[segments.length - 2])) {
this.resourceFullName = trimmedPath;
}
} else {
// if path has odd segments, get the parent(dbs/db1 from
// dbs/db1/colls)
if (Utils.IsResourceType(segments[segments.length - 1])) {
this.resourceFullName = trimmedPath.substring(0, trimmedPath.lastIndexOf("/"));
}
}
} else {
this.resourceFullName = this.getResourceId().toLowerCase();
}
return this.resourceFullName;
}
public String getResolvedCollectionRid() {
return resolvedCollectionRid;
}
public void setResolvedCollectionRid(String resolvedCollectionRid) {
this.resolvedCollectionRid = resolvedCollectionRid;
}
public PartitionKeyRangeIdentity getPartitionKeyRangeIdentity() {
return partitionKeyRangeIdentity;
}
public void routeTo(PartitionKeyRangeIdentity partitionKeyRangeIdentity) {
this.setPartitionKeyRangeIdentity(partitionKeyRangeIdentity);
}
public void setPartitionKeyRangeIdentity(PartitionKeyRangeIdentity partitionKeyRangeIdentity) {
this.partitionKeyRangeIdentity = partitionKeyRangeIdentity;
if (partitionKeyRangeIdentity != null) {
this.headers.put(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID, partitionKeyRangeIdentity.toHeader());
} else {
this.headers.remove(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID);
}
}
public PartitionKeyRange getResolvedPartitionKeyRange() {
return resolvedPartitionKeyRange;
}
public void setResolvedPartitionKeyRange(PartitionKeyRange resolvedPartitionKeyRange) {
this.resolvedPartitionKeyRange = resolvedPartitionKeyRange;
}
public long getQuorumSelectedLSN() {
return quorumSelectedLSN;
}
public void setQuorumSelectedLSN(long quorumSelectedLSN) {
this.quorumSelectedLSN = quorumSelectedLSN;
}
public ConsistencyLevel getOriginalRequestConsistencyLevel() {
return originalRequestConsistencyLevel;
}
public void setOriginalRequestConsistencyLevel(ConsistencyLevel originalRequestConsistencyLevel) {
this.originalRequestConsistencyLevel = originalRequestConsistencyLevel;
}
public StoreResponse getGlobalStrongWriteResponse() {
return globalStrongWriteResponse;
}
public void setGlobalStrongWriteResponse(StoreResponse globalStrongWriteResponse) {
this.globalStrongWriteResponse = globalStrongWriteResponse;
}
public long getGlobalCommittedSelectedLSN() {
return globalCommittedSelectedLSN;
}
public void setGlobalCommittedSelectedLSN(long globalCommittedSelectedLSN) {
this.globalCommittedSelectedLSN = globalCommittedSelectedLSN;
}
public String getOriginalSessionToken() {
return originalSessionToken;
}
public void setOriginalSessionToken(String originalSessionToken) {
this.originalSessionToken = originalSessionToken;
}
public StoreReadResult getQuorumSelectedStoreResponse() {
return quorumSelectedStoreResponse;
}
public void setQuorumSelectedStoreResponse(StoreReadResult quorumSelectedStoreResponse) {
this.quorumSelectedStoreResponse = quorumSelectedStoreResponse;
}
public void setDefaultReplicaIndex(Integer defaultReplicaIndex) {
this.defaultReplicaIndex = defaultReplicaIndex;
}
public Integer getDefaultReplicaIndex() {
return defaultReplicaIndex;
}
public boolean isChangeFeedRequest() {
return this.headers.containsKey(HttpConstants.HttpHeaders.A_IM);
}
public boolean isWritingToMaster() {
return operationType.isWriteOperation() && resourceType.isMasterResource();
}
public boolean isReadingFromMaster() {
if (resourceType == ResourceType.Offer ||
resourceType == ResourceType.Database ||
resourceType == ResourceType.User ||
resourceType == ResourceType.Permission ||
resourceType == ResourceType.Topology ||
resourceType == ResourceType.DatabaseAccount ||
resourceType == ResourceType.PartitionKeyRange ||
(resourceType == ResourceType.DocumentCollection
&& (operationType == OperationType.ReadFeed
|| operationType == OperationType.Query
|| operationType == OperationType.SqlQuery))) {
return true;
}
return false;
}
public boolean isReadOnlyRequest() {
return Utils.isReadOnlyOperation(this.operationType);
}
public boolean shouldClearSessionTokenOnSessionReadFailure() {
return this.shouldClearSessionTokenOnSessionReadFailure;
}
public void setShouldClearSessionTokenOnSessionReadFailure(boolean value) {
this.shouldClearSessionTokenOnSessionReadFailure = value;
}
public Boolean getUsePreferredLocations() {
return this.usePreferredLocations;
}
public void setUsePreferredLocations(Boolean value) {
this.usePreferredLocations = value;
}
public Integer getLocationIndexToRoute() {
return this.locationIndexToRoute;
}
public void setLocationIndexToRoute(Integer value) {
this.locationIndexToRoute = value;
}
public URI getLocationEndpointToRoute() {
return this.locationEndpointToRoute;
}
public void setLocationEndpointToRoute(URI value) {
this.locationEndpointToRoute = value;
}
public ClientSideRequestStatistics getClientSideRequestStatistics() {
return clientSideRequestStatistics;
}
public void setClientSideRequestStatistics(ClientSideRequestStatistics clientSideRequestStatistics) {
this.clientSideRequestStatistics = clientSideRequestStatistics;
}
/**
* Sets routing directive for GlobalEndpointManager to resolve
* the request to endpoint based on location index
*
* @param locationIndex Index of the location to which the request should be routed.
* @param usePreferredLocations Use preferred locations to route request.
*/
public void routeToLocation(int locationIndex, boolean usePreferredLocations) {
this.setLocationIndexToRoute(locationIndex);
this.setUsePreferredLocations(usePreferredLocations);
this.setLocationEndpointToRoute(null);
}
/**
* Sets location-based routing directive for GlobalEndpointManager to resolve
* the request to given locationEndpoint
*
* @param locationEndpoint Location endpoint to which the request should be routed.
*/
public void routeToLocation(URI locationEndpoint) {
this.setLocationEndpointToRoute(locationEndpoint);
this.setLocationIndexToRoute(null);
this.setUsePreferredLocations(null);
}
/**
* Clears location-based routing directive
*/
public void clearRouteToLocation() {
this.setLocationIndexToRoute(null);
this.setLocationEndpointToRoute(null);
this.setUsePreferredLocations(null);
}
public String getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
boolean isUserProvidedPartitionKeyRangeIdentity() {
return isUserProvidedPartitionKeyRangeIdentity;
}
void setUserProvidedPartitionKeyRangeIdentity(boolean userProvidedPartitionKeyRangeIdentity) {
isUserProvidedPartitionKeyRangeIdentity = userProvidedPartitionKeyRangeIdentity;
}
}