BuilderHelper.java
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.data.tables;
import com.azure.core.credential.AzureNamedKeyCredential;
import com.azure.core.credential.AzureSasCredential;
import com.azure.core.credential.TokenCredential;
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpHeader;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpPipelineBuilder;
import com.azure.core.http.policy.AddDatePolicy;
import com.azure.core.http.policy.AddHeadersPolicy;
import com.azure.core.http.policy.AzureSasCredentialPolicy;
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.http.policy.HttpLoggingPolicy;
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.http.policy.HttpPolicyProviders;
import com.azure.core.http.policy.RequestIdPolicy;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.core.http.policy.UserAgentPolicy;
import com.azure.core.util.ClientOptions;
import com.azure.core.util.Configuration;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
import com.azure.data.tables.implementation.CosmosPatchTransformPolicy;
import com.azure.data.tables.implementation.NullHttpClient;
import com.azure.data.tables.implementation.StorageAuthenticationSettings;
import com.azure.data.tables.implementation.StorageConnectionString;
import com.azure.data.tables.implementation.StorageConstants;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
final class BuilderHelper {
private static final Map<String, String> PROPERTIES =
CoreUtils.getProperties("azure-data-tables.properties");
private static final String CLIENT_NAME = PROPERTIES.getOrDefault("name", "UnknownName");
private static final String CLIENT_VERSION = PROPERTIES.getOrDefault("version", "UnknownVersion");
private static final String COSMOS_ENDPOINT_SUFFIX = "cosmos.azure.com";
static HttpPipeline buildPipeline(AzureNamedKeyCredential azureNamedKeyCredential,
AzureSasCredential azureSasCredential, TokenCredential tokenCredential,
String sasToken, String endpoint, RetryPolicy retryPolicy,
HttpLogOptions logOptions, ClientOptions clientOptions, HttpClient httpClient,
List<HttpPipelinePolicy> perCallAdditionalPolicies,
List<HttpPipelinePolicy> perRetryAdditionalPolicies, Configuration configuration,
ClientLogger logger) {
configuration = (configuration == null) ? Configuration.getGlobalConfiguration() : configuration;
retryPolicy = (retryPolicy == null) ? new RetryPolicy() : retryPolicy;
logOptions = (logOptions == null) ? new HttpLogOptions() : logOptions;
// Closest to API goes first, closest to wire goes last.
List<HttpPipelinePolicy> policies = new ArrayList<>();
if (endpoint == null) {
throw logger.logExceptionAsError(
new IllegalStateException("An 'endpoint' is required to create a client. Use builders' 'endpoint()' or"
+ " 'connectionString()' methods to set this value."));
} else if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) {
policies.add(new CosmosPatchTransformPolicy());
}
policies.add(new UserAgentPolicy(
CoreUtils.getApplicationId(clientOptions, logOptions), CLIENT_NAME, CLIENT_VERSION, configuration));
policies.add(new RequestIdPolicy());
if (clientOptions != null) {
List<HttpHeader> httpHeaderList = new ArrayList<>();
clientOptions.getHeaders().forEach(header ->
httpHeaderList.add(new HttpHeader(header.getName(), header.getValue())));
policies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaderList)));
}
// Add per call additional policies.
policies.addAll(perCallAdditionalPolicies);
HttpPolicyProviders.addBeforeRetryPolicies(policies);
// Add retry policy.
policies.add(retryPolicy);
policies.add(new AddDatePolicy());
HttpPipelinePolicy credentialPolicy;
if (azureNamedKeyCredential != null) {
credentialPolicy = new TableAzureNamedKeyCredentialPolicy(azureNamedKeyCredential);
} else if (azureSasCredential != null) {
credentialPolicy = new AzureSasCredentialPolicy(azureSasCredential, false);
} else if (sasToken != null) {
credentialPolicy = new AzureSasCredentialPolicy(new AzureSasCredential(sasToken), false);
} else if (tokenCredential != null) {
credentialPolicy = new BearerTokenAuthenticationPolicy(tokenCredential, StorageConstants.STORAGE_SCOPE);
} else {
throw logger.logExceptionAsError(
new IllegalStateException("A form of authentication is required to create a client. Use a builder's "
+ "'credential()', 'sasToken()' or 'connectionString()' methods to set a form of authentication."));
}
policies.add(credentialPolicy);
// Add per retry additional policies.
policies.addAll(perRetryAdditionalPolicies);
HttpPolicyProviders.addAfterRetryPolicies(policies); //should this be between 3/4?
policies.add(new HttpLoggingPolicy(logOptions));
policies.add(new TableScrubEtagPolicy());
return new HttpPipelineBuilder()
.policies(policies.toArray(new HttpPipelinePolicy[0]))
.httpClient(httpClient)
.build();
}
static HttpPipeline buildNullClientPipeline() {
HttpPipelinePolicy[] policies = {
new AddHeadersPolicy(new HttpHeaders().put("Accept", "application/json;odata=minimalmetadata"))
};
return new HttpPipelineBuilder()
.policies(policies)
.httpClient(new NullHttpClient())
.build();
}
static void validateCredentials(AzureNamedKeyCredential azureNamedKeyCredential,
AzureSasCredential azureSasCredential, TokenCredential tokenCredential,
String sasToken, String connectionString, ClientLogger logger) {
List<Object> usedCredentials =
Stream.of(azureNamedKeyCredential, azureSasCredential, tokenCredential, sasToken, connectionString)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// Only allow two forms of authentication when 'connectionString' and 'sasToken' are provided. Validate that
// both contain the same SAS settings.
if (usedCredentials.size() == 2 && connectionString != null && sasToken != null) {
StorageConnectionString storageConnectionString =
StorageConnectionString.create(connectionString, logger);
StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings();
if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) {
if (sasToken.equals(authSettings.getSasToken())) {
return;
} else {
throw logger.logExceptionAsError(new IllegalStateException("'connectionString' contains a SAS token"
+ " with different settings than the one provided using the builder's 'sasToken()' method."));
}
} else if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) {
throw logger.logExceptionAsError(new IllegalStateException("A 'connectionString' containing an account"
+ "name and key cannot be provided alongside a 'sasToken'."));
}
// If the 'connectionString' auth type is not SAS_TOKEN and a 'sasToken' was provided, then multiple
// incompatible forms of authentication were specified in the client builder.
}
if (usedCredentials.size() > 1) {
StringJoiner usedCredentialsStringBuilder = new StringJoiner(", ");
if (azureNamedKeyCredential != null) {
usedCredentialsStringBuilder.add("azureNamedKeyCredential");
}
if (azureSasCredential != null) {
usedCredentialsStringBuilder.add("azureSasCredential");
}
if (tokenCredential != null) {
usedCredentialsStringBuilder.add("tokenCredential");
}
if (sasToken != null) {
usedCredentialsStringBuilder.add("sasToken");
}
if (connectionString != null) {
usedCredentialsStringBuilder.add("connectionString");
}
throw logger.logExceptionAsError(new IllegalStateException(
"Only one form of authentication should be used. The authentication forms present are: "
+ usedCredentialsStringBuilder + "."));
}
}
}