diff --git a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java index dc5ef9dc7d..9e39a0e9b7 100644 --- a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java +++ b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java @@ -131,8 +131,9 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws // Set defaults Properties authSettings = new Properties(); authSettings.put(CredentialSettings.AUTH_URL.toString(), AzureEnvironment.AZURE.getAuthenticationEndpoint()); - authSettings.put(CredentialSettings.BASE_URL.toString(), AzureEnvironment.AZURE.getBaseUrl()); + authSettings.put(CredentialSettings.BASE_URL.toString(), AzureEnvironment.AZURE.getResourceManagerEndpoint()); authSettings.put(CredentialSettings.MANAGEMENT_URI.toString(), AzureEnvironment.AZURE.getManagementEndpoint()); + authSettings.put(CredentialSettings.GRAPH_URL.toString(), AzureEnvironment.AZURE.getGraphEndpoint()); // Load the credentials from the file FileInputStream credentialsFileStream = new FileInputStream(credentialsFile); diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java index 421bc2869d..fcb139e64d 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java @@ -7,6 +7,8 @@ package com.microsoft.azure; +import java.lang.reflect.Field; + /** * An instance of this class describes an environment in Azure. */ @@ -14,7 +16,7 @@ public final class AzureEnvironment { /** * Base URL for calls to Azure management API. */ - private final String resourceManagerEndpoint; + private String resourceManagerEndpoint; /** * ActiveDirectory Endpoint for the authentications. @@ -98,19 +100,10 @@ public AzureEnvironment( * * @return the Base URL for the management service. */ - public String getBaseUrl() { + public String getResourceManagerEndpoint() { return this.resourceManagerEndpoint; } - /** - * @return a builder for the rest client. - */ - public RestClient.Builder.Buildable newRestClientBuilder() { - return new RestClient.Builder() - .withDefaultBaseUrl(this) - .withInterceptor(new RequestIdHeaderInterceptor()); - } - /** * @return the ActiveDirectory Endpoint for the Azure Environment. */ @@ -153,4 +146,59 @@ public boolean isValidateAuthority() { public void setValidateAuthority(boolean validateAuthority) { this.validateAuthority = validateAuthority; } + + /** + * The enum representing available endpoints in an environment. + */ + public enum Endpoint { + /** Azure Resource Manager endpoint. */ + RESOURCE_MANAGER("resourceManagerEndpoint"), + /** Azure Active Directory Graph APIs endpoint. */ + GRAPH("graphEndpoint"); + + private String field; + + Endpoint(String value) { + this.field = value; + } + + @Override + public String toString() { + return field; + } + } + + /** + * Get the endpoint URL for the current environment. + * + * @param endpoint the endpoint + * @return the URL + */ + public String getEndpoint(Endpoint endpoint) { + try { + Field f = AzureEnvironment.class.getDeclaredField(endpoint.toString()); + f.setAccessible(true); + return (String) f.get(this); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Unable to reflect on field " + endpoint.toString(), e); + } + } + + /** + * Create a builder for rest client from an endpoint. + * + * @param endpoint the endpoint + * @return a RestClient builder + */ + public RestClient.Builder newRestClientBuilder(Endpoint endpoint) { + return new RestClient.Builder().withBaseUrl(this, endpoint); + } + + /** + * Create a builder for rest client to Azure Resource Manager. + * @return a RestClient builder + */ + public RestClient.Builder newRestClientBuilder() { + return new RestClient.Builder().withBaseUrl(this, Endpoint.RESOURCE_MANAGER); + } } diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceClient.java b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceClient.java index 09c8c28fbd..644a68ff8e 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceClient.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceClient.java @@ -22,8 +22,7 @@ public abstract class AzureServiceClient { private RestClient restClient; protected AzureServiceClient(String baseUrl) { - this(new RestClient.Builder().withBaseUrl(baseUrl) - .withInterceptor(new RequestIdHeaderInterceptor()).build()); + this(new RestClient.Builder().withBaseUrl(baseUrl).build()); } /** diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/RestClient.java b/azure-client-runtime/src/main/java/com/microsoft/azure/RestClient.java index 8ff7d89c4c..df70d3295e 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/RestClient.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/RestClient.java @@ -22,7 +22,6 @@ import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; -import java.lang.reflect.Field; import java.net.CookieManager; import java.net.CookiePolicy; import java.net.Proxy; @@ -32,7 +31,7 @@ /** * An instance of this class stores the client information for making REST calls. */ -public class RestClient { +public final class RestClient { /** The {@link okhttp3.OkHttpClient} object. */ private OkHttpClient httpClient; /** The {@link retrofit2.Retrofit} object. */ @@ -41,26 +40,18 @@ public class RestClient { private ServiceClientCredentials credentials; /** The interceptor to handle custom headers. */ private CustomHeadersInterceptor customHeadersInterceptor; - /** The interceptor to handle base URL. */ - private BaseUrlHandler baseUrlHandler; /** The adapter to a Jackson {@link com.fasterxml.jackson.databind.ObjectMapper}. */ private JacksonMapperAdapter mapperAdapter; - /** The interceptor to set 'User-Agent' header. */ - private UserAgentInterceptor userAgentInterceptor; - protected RestClient(OkHttpClient httpClient, + private RestClient(OkHttpClient httpClient, Retrofit retrofit, ServiceClientCredentials credentials, CustomHeadersInterceptor customHeadersInterceptor, - UserAgentInterceptor userAgentInterceptor, - BaseUrlHandler baseUrlHandler, JacksonMapperAdapter mapperAdapter) { this.httpClient = httpClient; this.retrofit = retrofit; this.credentials = credentials; this.customHeadersInterceptor = customHeadersInterceptor; - this.userAgentInterceptor = userAgentInterceptor; - this.baseUrlHandler = baseUrlHandler; this.mapperAdapter = mapperAdapter; } @@ -120,26 +111,30 @@ public ServiceClientCredentials credentials() { return this.credentials; } + /** + * Create a new builder for a new Rest Client with the same configurations on this one. + * @return a RestClient builder + */ + public RestClient.Builder newBuilder() { + return new Builder(this); + } + /** * The builder class for building a REST client. */ public static class Builder { /** The dynamic base URL with variables wrapped in "{" and "}". */ - protected String baseUrl; + private String baseUrl; /** The builder to build an {@link OkHttpClient}. */ - protected OkHttpClient.Builder httpClientBuilder; + private OkHttpClient.Builder httpClientBuilder; /** The builder to build a {@link Retrofit}. */ - protected Retrofit.Builder retrofitBuilder; + private Retrofit.Builder retrofitBuilder; /** The credentials to authenticate. */ - protected ServiceClientCredentials credentials; + private ServiceClientCredentials credentials; /** The interceptor to handle custom headers. */ - protected CustomHeadersInterceptor customHeadersInterceptor; - /** The interceptor to handle base URL. */ - protected BaseUrlHandler baseUrlHandler; - /** The interceptor to set 'User-Agent' header. */ - protected UserAgentInterceptor userAgentInterceptor; - /** The inner Builder instance. */ - protected Buildable buildable; + private CustomHeadersInterceptor customHeadersInterceptor; + /** The value for 'User-Agent' header. */ + private String userAgent; /** * Creates an instance of the builder with a base URL to the service. @@ -148,6 +143,25 @@ public Builder() { this(new OkHttpClient.Builder(), new Retrofit.Builder()); } + private Builder(RestClient restClient) { + this(); + this.withBaseUrl(restClient.retrofit.baseUrl().toString()) + .withConnectionTimeout(restClient.httpClient.connectTimeoutMillis(), TimeUnit.MILLISECONDS) + .withReadTimeout(restClient.httpClient.readTimeoutMillis(), TimeUnit.MILLISECONDS); + if (restClient.credentials != null) { + this.withCredentials(restClient.credentials); + } + if (restClient.retrofit.callbackExecutor() != null) { + this.withCallbackExecutor(restClient.retrofit.callbackExecutor()); + } + for (Interceptor interceptor : restClient.httpClient.interceptors()) { + this.withInterceptor(interceptor); + } + for (Interceptor interceptor : restClient.httpClient.networkInterceptors()) { + this.withNetworkInterceptor(interceptor); + } + } + /** * Creates an instance of the builder with a base URL and 2 custom builders. * @@ -161,18 +175,15 @@ public Builder(OkHttpClient.Builder httpClientBuilder, Retrofit.Builder retrofit if (retrofitBuilder == null) { throw new IllegalArgumentException("retrofitBuilder == null"); } + this.baseUrl = AzureEnvironment.AZURE.getResourceManagerEndpoint(); CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); customHeadersInterceptor = new CustomHeadersInterceptor(); - baseUrlHandler = new BaseUrlHandler(); - userAgentInterceptor = new UserAgentInterceptor(); // Set up OkHttp client this.httpClientBuilder = httpClientBuilder .cookieJar(new JavaNetCookieJar(cookieManager)) - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(userAgentInterceptor); + .readTimeout(30, TimeUnit.SECONDS); this.retrofitBuilder = retrofitBuilder; - this.buildable = new Buildable(); } /** @@ -181,174 +192,178 @@ public Builder(OkHttpClient.Builder httpClientBuilder, Retrofit.Builder retrofit * @param baseUrl the base URL to use. * @return the builder itself for chaining. */ - public Buildable withBaseUrl(String baseUrl) { + public Builder withBaseUrl(String baseUrl) { this.baseUrl = baseUrl; - return buildable; + return this; } /** - * Sets the base URL with the default from the service client. + * Sets the base URL with the default from the Azure Environment. * - * @param serviceClientClass the service client class containing a default base URL. + * @param environment the Azure environment to use + * @param endpoint the environment endpoint the application is accessing + * @return the builder itself for chaining + */ + public Builder withBaseUrl(AzureEnvironment environment, AzureEnvironment.Endpoint endpoint) { + this.baseUrl = environment.getEndpoint(endpoint); + return this; + } + + /** + * Sets the credentials. + * + * @param credentials the credentials object. * @return the builder itself for chaining. */ - public Buildable withDefaultBaseUrl(Class serviceClientClass) { - try { - Field field = serviceClientClass.getDeclaredField("DEFAULT_BASE_URL"); - field.setAccessible(true); - baseUrl = (String) field.get(null); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new UnsupportedOperationException("Cannot read static field DEFAULT_BASE_URL", e); + public Builder withCredentials(ServiceClientCredentials credentials) { + if (credentials == null) { + throw new NullPointerException("credentials == null"); } - return buildable; + this.credentials = credentials; + credentials.applyCredentialsFilter(httpClientBuilder); + + return this; } /** - * Sets the base URL with the default from the Azure Environment. + * Sets the user agent header. * - * @param environment the environment the application is running in - * @return the builder itself for chaining + * @param userAgent the user agent header. + * @return the builder itself for chaining. */ - public RestClient.Builder.Buildable withDefaultBaseUrl(AzureEnvironment environment) { - withBaseUrl(environment.getBaseUrl()); - return buildable; + public Builder withUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; } /** - * The inner class from which a Rest Client can be built. + * Sets the log level. + * + * @param logLevel the {@link okhttp3.logging.HttpLoggingInterceptor.Level} enum. + * @return the builder itself for chaining. */ - public class Buildable { - /** - * Sets the user agent header. - * - * @param userAgent the user agent header. - * @return the builder itself for chaining. - */ - public Buildable withUserAgent(String userAgent) { - userAgentInterceptor.withUserAgent(userAgent); - return this; - } - - /** - * Sets the credentials. - * - * @param credentials the credentials object. - * @return the builder itself for chaining. - */ - public Buildable withCredentials(ServiceClientCredentials credentials) { - Builder.this.credentials = credentials; - if (credentials != null) { - credentials.applyCredentialsFilter(httpClientBuilder); - } - return this; + public Builder withLogLevel(HttpLoggingInterceptor.Level logLevel) { + if (logLevel == null) { + throw new NullPointerException("logLevel == null"); } + httpClientBuilder.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(logLevel)); + return this; + } - /** - * Sets the log level. - * - * @param logLevel the {@link okhttp3.logging.HttpLoggingInterceptor.Level} enum. - * @return the builder itself for chaining. - */ - public Buildable withLogLevel(HttpLoggingInterceptor.Level logLevel) { - httpClientBuilder.addInterceptor(new HttpLoggingInterceptor().setLevel(logLevel)); - return this; + /** + * Add an interceptor the Http client pipeline. + * + * @param interceptor the interceptor to add. + * @return the builder itself for chaining. + */ + public Builder withInterceptor(Interceptor interceptor) { + if (interceptor == null) { + throw new NullPointerException("interceptor == null"); } + httpClientBuilder.addInterceptor(interceptor); + return this; + } - /** - * Add an interceptor the Http client pipeline. - * - * @param interceptor the interceptor to add. - * @return the builder itself for chaining. - */ - public Buildable withInterceptor(Interceptor interceptor) { - httpClientBuilder.addInterceptor(interceptor); - return this; + /** + * Add an interceptor the network layer of Http client pipeline. + * + * @param networkInterceptor the interceptor to add. + * @return the builder itself for chaining. + */ + public Builder withNetworkInterceptor(Interceptor networkInterceptor) { + if (networkInterceptor == null) { + throw new NullPointerException("networkInterceptor == null"); } + httpClientBuilder.addNetworkInterceptor(networkInterceptor); + return this; + } - /** - * Set the read timeout on the HTTP client. Default is 10 seconds. - * - * @param timeout the timeout numeric value - * @param unit the time unit for the numeric value - * @return the builder itself for chaining - */ - public Buildable withReadTimeout(long timeout, TimeUnit unit) { - httpClientBuilder.readTimeout(timeout, unit); - return this; - } + /** + * Set the read timeout on the HTTP client. Default is 10 seconds. + * + * @param timeout the timeout numeric value + * @param unit the time unit for the numeric value + * @return the builder itself for chaining + */ + public Builder withReadTimeout(long timeout, TimeUnit unit) { + httpClientBuilder.readTimeout(timeout, unit); + return this; + } - /** - * Set the connection timeout on the HTTP client. Default is 10 seconds. - * - * @param timeout the timeout numeric value - * @param unit the time unit for the numeric value - * @return the builder itself for chaining - */ - public Buildable withConnectionTimeout(long timeout, TimeUnit unit) { - httpClientBuilder.connectTimeout(timeout, unit); - return this; - } + /** + * Set the connection timeout on the HTTP client. Default is 10 seconds. + * + * @param timeout the timeout numeric value + * @param unit the time unit for the numeric value + * @return the builder itself for chaining + */ + public Builder withConnectionTimeout(long timeout, TimeUnit unit) { + httpClientBuilder.connectTimeout(timeout, unit); + return this; + } - /** - * Set the maximum idle connections for the HTTP client. Default is 5. - * - * @param maxIdleConnections the maximum idle connections - * @return the builder itself for chaining - */ - public Buildable withMaxIdleConnections(int maxIdleConnections) { - httpClientBuilder.connectionPool(new ConnectionPool(maxIdleConnections, 5, TimeUnit.MINUTES)); - return this; - } + /** + * Set the maximum idle connections for the HTTP client. Default is 5. + * + * @param maxIdleConnections the maximum idle connections + * @return the builder itself for chaining + */ + public Builder withMaxIdleConnections(int maxIdleConnections) { + httpClientBuilder.connectionPool(new ConnectionPool(maxIdleConnections, 5, TimeUnit.MINUTES)); + return this; + } - /** - * Sets the executor for async callbacks to run on. - * - * @param executor the executor to execute the callbacks. - * @return the builder itself for chaining - */ - public Buildable withCallbackExecutor(Executor executor) { - retrofitBuilder.callbackExecutor(executor); - return this; - } + /** + * Sets the executor for async callbacks to run on. + * + * @param executor the executor to execute the callbacks. + * @return the builder itself for chaining + */ + public Builder withCallbackExecutor(Executor executor) { + retrofitBuilder.callbackExecutor(executor); + return this; + } - /** - * Sets the proxy for the HTTP client. - * - * @param proxy the proxy to use - * @return the builder itself for chaining - */ - public Buildable withProxy(Proxy proxy) { - httpClientBuilder.proxy(proxy); - return this; - } + /** + * Sets the proxy for the HTTP client. + * + * @param proxy the proxy to use + * @return the builder itself for chaining + */ + public Builder withProxy(Proxy proxy) { + httpClientBuilder.proxy(proxy); + return this; + } - /** - * Build a RestClient with all the current configurations. - * - * @return a {@link RestClient}. - */ - public RestClient build() { - AzureJacksonMapperAdapter mapperAdapter = new AzureJacksonMapperAdapter(); - OkHttpClient httpClient = httpClientBuilder - .addInterceptor(baseUrlHandler) - .addInterceptor(customHeadersInterceptor) - .addInterceptor(new RetryHandler(new ResourceGetExponentialBackoffRetryStrategy())) - .addInterceptor(new RetryHandler()) - .build(); - return new RestClient(httpClient, - retrofitBuilder - .baseUrl(baseUrl) - .client(httpClient) - .addConverterFactory(mapperAdapter.getConverterFactory()) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .build(), - credentials, - customHeadersInterceptor, - userAgentInterceptor, - baseUrlHandler, - mapperAdapter); + /** + * Build a RestClient with all the current configurations. + * + * @return a {@link RestClient}. + */ + public RestClient build() { + AzureJacksonMapperAdapter mapperAdapter = new AzureJacksonMapperAdapter(); + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(); + if (userAgent != null) { + userAgentInterceptor.withUserAgent(userAgent); } - + OkHttpClient httpClient = httpClientBuilder + .addInterceptor(userAgentInterceptor) + .addInterceptor(new RequestIdHeaderInterceptor()) + .addInterceptor(new BaseUrlHandler()) + .addInterceptor(customHeadersInterceptor) + .addInterceptor(new RetryHandler(new ResourceGetExponentialBackoffRetryStrategy())) + .addInterceptor(new RetryHandler()) + .build(); + return new RestClient(httpClient, + retrofitBuilder + .baseUrl(baseUrl) + .client(httpClient) + .addConverterFactory(mapperAdapter.getConverterFactory()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build(), + credentials, + customHeadersInterceptor, + mapperAdapter); } } } diff --git a/azure-client-runtime/src/test/java/com/microsoft/azure/RequestIdHeaderInterceptorTests.java b/azure-client-runtime/src/test/java/com/microsoft/azure/RequestIdHeaderInterceptorTests.java index 060336cd23..bd91e19db5 100644 --- a/azure-client-runtime/src/test/java/com/microsoft/azure/RequestIdHeaderInterceptorTests.java +++ b/azure-client-runtime/src/test/java/com/microsoft/azure/RequestIdHeaderInterceptorTests.java @@ -8,16 +8,14 @@ package com.microsoft.azure; import com.microsoft.rest.retry.RetryHandler; - -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; - import okhttp3.Interceptor; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; public class RequestIdHeaderInterceptorTests { private static final String REQUEST_ID_HEADER = "x-ms-client-request-id"; diff --git a/client-runtime/src/main/java/com/microsoft/rest/UserAgentInterceptor.java b/client-runtime/src/main/java/com/microsoft/rest/UserAgentInterceptor.java index 7b822078d7..eabf9c09c2 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/UserAgentInterceptor.java +++ b/client-runtime/src/main/java/com/microsoft/rest/UserAgentInterceptor.java @@ -57,6 +57,13 @@ public UserAgentInterceptor appendUserAgent(String userAgent) { return this; } + /** + * @return the current user agent string. + */ + public String userAgent() { + return userAgent; + } + @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); @@ -64,7 +71,7 @@ public Response intercept(Chain chain) throws IOException { if (header == null) { header = DEFAULT_USER_AGENT_HEADER; } - if (!userAgent.equals(DEFAULT_USER_AGENT_HEADER)) { + if (!DEFAULT_USER_AGENT_HEADER.equals(userAgent)) { if (header.equals(DEFAULT_USER_AGENT_HEADER)) { header = userAgent; } else {