All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.documentdb.GlobalEndpointManager Maven / Gradle / Ivy

package com.microsoft.azure.documentdb;

import com.microsoft.azure.documentdb.LocationCache.CanRefreshInBackground;
import com.microsoft.azure.documentdb.internal.AsyncCache;
import com.microsoft.azure.documentdb.internal.Constants;
import com.microsoft.azure.documentdb.internal.DocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.EndpointManager;
import com.microsoft.azure.documentdb.internal.HttpConstants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * This class implements the logic for endpoint management for geo-replicated
 * database accounts.
 * 

* When ConnectionPolicy.getEnableEndpointDiscovery is true, * the GlobalEndpointManager will choose the correct endpoint to use for write * and read operations based on database account information retrieved from the * service in conjunction with user's preference as specified in * ConnectionPolicy().getPreferredLocations. */ class GlobalEndpointManager implements EndpointManager { private final DatabaseAccountManagerInternal client; private final Logger logger = LoggerFactory.getLogger(GlobalEndpointManager.class); private final boolean enableEndpointDiscovery; private final URI defaultEndpoint; private final Object refreshLock; private DocumentClient documentClient; private LocationCache locationCache; private Future endpointRefreshTaskFuture; private long backgroundRefreshLocationTimeIntervalInMS; private volatile boolean isRefreshing; private AsyncCache databaseAccountCache; public GlobalEndpointManager(final DocumentClient client) { this(new DatabaseAccountManagerInternal() { @Override public URI getServiceEndpoint() { return client.getServiceEndpoint(); } @Override public DatabaseAccount getDatabaseAccountFromEndpoint(URI endpoint) throws DocumentClientException { return client.getDatabaseAccountFromEndpoint(endpoint); } @Override public ConnectionPolicy getConnectionPolicy() { return client.getConnectionPolicy(); } }); this.documentClient = client; this.databaseAccountCache = new AsyncCache(client.getExecutorService()); } public GlobalEndpointManager(DatabaseAccountManagerInternal client) { this.client = client; this.enableEndpointDiscovery = client.getConnectionPolicy().getEnableEndpointDiscovery(); Collection preferredLocations = client.getConnectionPolicy().getPreferredLocations() != null ? Collections.unmodifiableCollection(client.getConnectionPolicy().getPreferredLocations()) : null; this.defaultEndpoint = client.getServiceEndpoint(); this.backgroundRefreshLocationTimeIntervalInMS = getBackgroundRefreshLocationTimeIntervalInMS(); this.locationCache = createLocationCache(preferredLocations, client.getServiceEndpoint(), this.enableEndpointDiscovery, client.getConnectionPolicy().isUsingMultipleWriteLocations(), this.backgroundRefreshLocationTimeIntervalInMS); this.isRefreshing = false; this.refreshLock = new Object(); } long getBackgroundRefreshLocationTimeIntervalInMS() { return Constants.Properties.DEFAULT_UNAVAILABLE_LOCATION_EXPIRATION_TIME; } LocationCache createLocationCache(Collection preferredLocations, URI serviceEndpoint, boolean enableEndpointDiscovery, boolean useMultipleWriteLocations, long defaultUnavailableLocationExpirationTime) { return new LocationCache(preferredLocations, serviceEndpoint, enableEndpointDiscovery, useMultipleWriteLocations, defaultUnavailableLocationExpirationTime); } @Override public URI getWriteEndpoint() { return this.locationCache.getWriteEndpoint(); } @Override public URI getReadEndpoint() { return this.locationCache.getReadEndpoint(); } @Override public List getOrderedWriteEndpoints() { return this.locationCache.getOrderedWriteEndpoints(); } @Override public List getOrderedReadEndpoints() { return this.locationCache.getOrderedReadEndpoints(); } public DatabaseAccount getDatabaseAccountFromAnyEndpoint() { DatabaseAccount databaseAccount = null; try { databaseAccount = this.client.getDatabaseAccountFromEndpoint(this.defaultEndpoint); } catch (DocumentClientException e) { this.logger.warn("Failed to retrieve database account information. {}", e.toString()); } // The global endpoint was not working. Try other endpoints in the preferred read region list. Collection preferredLocations = this.locationCache.getPreferredLocations(); if (databaseAccount == null && preferredLocations != null && preferredLocations.size() > 0) { for (String regionName : preferredLocations) { URI regionalUri = this.getRegionalEndpoint(regionName); if (regionalUri != null) { try { databaseAccount = this.client.getDatabaseAccountFromEndpoint(regionalUri); break; } catch (DocumentClientException e) { this.logger.warn("Failed to retrieve database account information. {}", e.toString()); } } } } return databaseAccount; } @Override public URI resolveServiceEndpoint(DocumentServiceRequest request) { return this.locationCache.resolveServiceEndpoint(request); } @Override public void markEndpointUnavailableForRead(URI endpoint) { //this.logger.warn(String.format("Marking endpoint %s unavailable for Read", endpoint.toString())); this.locationCache.markCurrentLocationUnavailableForRead(endpoint); } @Override public void markEndpointUnavailableForWrite(URI endpoint) { //this.logger.warn(String.format("Marking endpoint %s unavailable for Write", endpoint.toString())); this.locationCache.markCurrentLocationUnavailableForWrite(endpoint); } public boolean canUseMultipleWriteLocations(DocumentServiceRequest request) { return this.locationCache.canUseMultipleWriteLocations(request); } public void close() { if (endpointRefreshTaskFuture != null) { endpointRefreshTaskFuture.cancel(true); } } URI getRegionalEndpoint(String regionName) { if (StringUtils.isNotEmpty(regionName)) { String databaseAccountName = this.defaultEndpoint.getHost(); int indexOfDot = this.defaultEndpoint.getHost().indexOf('.'); if (indexOfDot >= 0) { databaseAccountName = databaseAccountName.substring(0, indexOfDot); } // Add region name suffix to the account name. String regionalAccountName = databaseAccountName + "-" + regionName.replace(" ", ""); String regionalUrl = this.defaultEndpoint.toString().replaceFirst(databaseAccountName, regionalAccountName); try { return new URI(regionalUrl); } catch (URISyntaxException e) { return null; } } return null; } @Override public void refreshEndpointList(DatabaseAccount databaseAccount, boolean forceRefresh) { if (forceRefresh) { DatabaseAccount refreshedDatabaseAccount = this.refreshDatabaseAccountInternal(); this.locationCache.onDatabaseAccountRead(refreshedDatabaseAccount); return; } this.refreshEndpointList(databaseAccount); } @Override public synchronized void refreshEndpointList(DatabaseAccount databaseAccount) { synchronized (this.refreshLock) { if (this.isRefreshing) return; this.isRefreshing = true; } try { this.refreshEndpointListPrivate(databaseAccount); } catch (Exception e) { this.isRefreshing = false; throw e; } } public synchronized void refreshEndpointListPrivate(DatabaseAccount databaseAccount) { if (databaseAccount != null) { logger.debug("Refreshing endpoints list"); this.locationCache.onDatabaseAccountRead(databaseAccount); } CanRefreshInBackground canRefreshInBackground = new CanRefreshInBackground(false); if (this.locationCache.shouldRefreshEndpoints(canRefreshInBackground)) { if (databaseAccount == null && !canRefreshInBackground.getValue()) { logger.debug("Refreshing endpoints list"); databaseAccount = this.refreshDatabaseAccountInternal(); this.locationCache.onDatabaseAccountRead(databaseAccount); } this.startRefreshLocationTimer(); } else { this.isRefreshing = false; } } public void startRefreshLocationTimer() { final GlobalEndpointManager that = this; endpointRefreshTaskFuture = this.documentClient.getExecutorService().submit(new Runnable() { @Override public void run() { try { Thread.sleep(that.backgroundRefreshLocationTimeIntervalInMS); DatabaseAccount databaseAccount = that.refreshDatabaseAccountInternal(); that.refreshEndpointListPrivate(databaseAccount); } catch (Exception e) { logger.warn("Preferred location background task was interrupted"); that.startRefreshLocationTimer(); } } }); } public DatabaseAccount getDatabaseAccountFromCache() { try { final GlobalEndpointManager that = this; return this.databaseAccountCache.get(StringUtils.EMPTY, null, new Callable() { @Override public DatabaseAccount call() throws Exception { DatabaseAccount databaseAccount = that.getDatabaseAccountFromAnyEndpoint(); GlobalEndpointManager.this.refreshEndpointList(databaseAccount); return databaseAccount; } }).get(); } catch (Exception e) { throw new IllegalStateException(e); } } private DatabaseAccount refreshDatabaseAccountInternal() { final GlobalEndpointManager that = this; Callable fetchDatabaseAccount = new Callable() { @Override public DatabaseAccount call() throws Exception { return that.getDatabaseAccountFromAnyEndpoint(); } }; try { DatabaseAccount obsoleteDatabaseAccount = this.databaseAccountCache.get(StringUtils.EMPTY, null, fetchDatabaseAccount).get(); DatabaseAccount newDatabaseAccount = this.databaseAccountCache.get(StringUtils.EMPTY, obsoleteDatabaseAccount, fetchDatabaseAccount).get(); if(newDatabaseAccount == null && obsoleteDatabaseAccount != null) { this.databaseAccountCache.put(StringUtils.EMPTY, obsoleteDatabaseAccount); } return newDatabaseAccount; } catch (Exception e) { throw new IllegalStateException(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy