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

com.azure.spring.cloud.config.implementation.AppConfigurationReplicaClientsBuilder Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.cloud.config.implementation;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.azure.core.credential.TokenCredential;
import com.azure.core.http.policy.ExponentialBackoff;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;
import com.azure.spring.cloud.config.AppConfigurationCredentialProvider;
import com.azure.spring.cloud.config.ConfigurationClientBuilderSetup;
import com.azure.spring.cloud.config.pipline.policies.BaseAppConfigurationPolicy;
import com.azure.spring.cloud.config.properties.ConfigStore;

public class AppConfigurationReplicaClientsBuilder implements EnvironmentAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(AppConfigurationReplicaClientsBuilder.class);

    /**
     * Invalid Connection String error message
     */
    public static final String NON_EMPTY_MSG = "%s property should not be null or empty in the connection string of Azure Config Service.";

    private static final Duration DEFAULT_MIN_RETRY_POLICY = Duration.ofMillis(800);

    private static final Duration DEFAULT_MAX_RETRY_POLICY = Duration.ofSeconds(8);

    /**
     * Connection String Regex format
     */
    private static final String CONN_STRING_REGEXP = "Endpoint=([^;]+);Id=([^;]+);Secret=([^;]+)";

    /**
     * Invalid Formatted Connection String Error message
     */
    public static final String ENDPOINT_ERR_MSG = String.format("Connection string does not follow format %s.",
        CONN_STRING_REGEXP);

    private static final Pattern CONN_STRING_PATTERN = Pattern.compile(CONN_STRING_REGEXP);

    private AppConfigurationCredentialProvider tokenCredentialProvider;

    private ConfigurationClientBuilderSetup clientProvider;

    private boolean isDev = false;

    private boolean isKeyVaultConfigured = false;

    private String clientId = "";

    private final int maxRetries;

    public AppConfigurationReplicaClientsBuilder(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    /**
     * @param tokenCredentialProvider the tokenCredentialProvider to set
     */
    public void setTokenCredentialProvider(AppConfigurationCredentialProvider tokenCredentialProvider) {
        this.tokenCredentialProvider = tokenCredentialProvider;
    }

    /**
     * @param clientProvider the clientProvider to set
     */
    public void setClientProvider(ConfigurationClientBuilderSetup clientProvider) {
        this.clientProvider = clientProvider;
    }

    /**
     * @param isKeyVaultConfigured the isKeyVaultConfigured to set
     */
    public void setKeyVaultConfigured(boolean isKeyVaultConfigured) {
        this.isKeyVaultConfigured = isKeyVaultConfigured;
    }

    /**
     * @param clientId the clientId to set
     */
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    /**
     * Given a connection string, returns the endpoint value inside of it.
     * @param connectionString connection string to app configuration
     * @return endpoint
     * @throws IllegalStateException when connection string isn't valid.
     */
    public static String getEndpointFromConnectionString(String connectionString) {
        Assert.hasText(connectionString, "Connection string cannot be empty.");

        Matcher matcher = CONN_STRING_PATTERN.matcher(connectionString);
        if (!matcher.find()) {
            throw new IllegalStateException(ENDPOINT_ERR_MSG);
        }

        String endpoint = matcher.group(1);

        Assert.hasText(endpoint, String.format(NON_EMPTY_MSG, "Endpoint"));

        return endpoint;
    }

    /**
     * Builds all the clients for a connection.
     * @throws IllegalArgumentException when more than 1 connection method is given.
     */
    List buildClients(ConfigStore configStore) {
        List clients = new ArrayList<>();
        // Single client or Multiple?
        // If single call buildClient
        int hasSingleConnectionString = StringUtils.hasText(configStore.getConnectionString()) ? 1 : 0;
        int hasMultiEndpoints = configStore.getEndpoints().size() > 0 ? 1 : 0;
        int hasMultiConnectionString = configStore.getConnectionStrings().size() > 0 ? 1 : 0;

        if (hasSingleConnectionString + hasMultiEndpoints + hasMultiConnectionString > 1) {
            throw new IllegalArgumentException(
                "More than 1 Connection method was set for connecting to App Configuration.");
        }

        TokenCredential tokenCredential = null;

        if (tokenCredentialProvider != null) {
            tokenCredential = tokenCredentialProvider.getAppConfigCredential(configStore.getEndpoint());
        }

        boolean clientIdIsPresent = StringUtils.hasText(clientId);
        boolean tokenCredentialIsPresent = tokenCredential != null;
        boolean connectionStringIsPresent = configStore.getConnectionString() != null;

        if ((tokenCredentialIsPresent || clientIdIsPresent)
            && connectionStringIsPresent) {
            throw new IllegalArgumentException(
                "More than 1 Connection method was set for connecting to App Configuration.");
        } else if (tokenCredential != null && clientIdIsPresent) {
            throw new IllegalArgumentException(
                "More than 1 Connection method was set for connecting to App Configuration.");
        }

        ConfigurationClientBuilder builder = getBuilder();

        if (configStore.getConnectionString() != null) {
            clients.add(buildClientConnectionString(configStore.getConnectionString(), builder, 0));
        } else if (configStore.getConnectionStrings().size() > 0) {
            for (String connectionString : configStore.getConnectionStrings()) {
                clients.add(buildClientConnectionString(connectionString, builder,
                    configStore.getConnectionStrings().size() - 1));
            }
        } else if (configStore.getEndpoints().size() > 0) {
            for (String endpoint : configStore.getEndpoints()) {
                clients.add(buildClientEndpoint(tokenCredential, endpoint, builder, clientIdIsPresent,
                    configStore.getEndpoints().size() - 1));
            }
        } else if (configStore.getEndpoint() != null) {
            clients.add(buildClientEndpoint(tokenCredential, configStore.getEndpoint(), builder, clientIdIsPresent, 0));
        }
        return clients;
    }

    /**
     * @return creates an instance of ConfigurationClientBuilder
     */
    ConfigurationClientBuilder getBuilder() {
        return new ConfigurationClientBuilder();
    }

    private AppConfigurationReplicaClient buildClientEndpoint(TokenCredential tokenCredential,
        String endpoint, ConfigurationClientBuilder builder, boolean clientIdIsPresent, Integer replicaCount)
        throws IllegalArgumentException {
        if (tokenCredential != null) {
            // User Provided Token Credential
            LOGGER.debug("Connecting to " + endpoint + " using AppConfigurationCredentialProvider.");
            builder.credential(tokenCredential);
        } else if (clientIdIsPresent) {
            // User Assigned Identity - Client ID through configuration file.
            LOGGER.debug("Connecting to " + endpoint + " using Client ID from configuration file.");
            ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder()
                .clientId(clientId);
            builder.credential(micBuilder.build());
        } else {
            // System Assigned Identity. Needs to be checked last as all of the above should have an Endpoint.
            LOGGER.debug("Connecting to " + endpoint
                + " using Azure System Assigned Identity or Azure User Assigned Identity.");
            ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder();
            builder.credential(micBuilder.build());
        }

        builder.endpoint(endpoint);

        return modifyAndBuildClient(builder, endpoint, replicaCount);
    }

    private AppConfigurationReplicaClient buildClientConnectionString(String connectionString,
        ConfigurationClientBuilder builder, Integer replicaCount)
        throws IllegalArgumentException {
        String endpoint = getEndpointFromConnectionString(connectionString);
        LOGGER.debug("Connecting to " + endpoint + " using Connecting String.");

        builder.connectionString(connectionString);

        return modifyAndBuildClient(builder, endpoint, replicaCount);
    }

    private AppConfigurationReplicaClient modifyAndBuildClient(ConfigurationClientBuilder builder, String endpoint,
        Integer replicaCount) {
        ExponentialBackoff retryPolicy = new ExponentialBackoff(maxRetries, DEFAULT_MIN_RETRY_POLICY,
            DEFAULT_MAX_RETRY_POLICY);

        builder.addPolicy(new BaseAppConfigurationPolicy(isDev, isKeyVaultConfigured, replicaCount))
            .retryPolicy(new RetryPolicy(retryPolicy));

        if (clientProvider != null) {
            clientProvider.setup(builder, endpoint);
        }

        return new AppConfigurationReplicaClient(endpoint, builder.buildClient());
    }

    @Override
    public void setEnvironment(Environment environment) {
        for (String profile : environment.getActiveProfiles()) {
            if ("dev".equalsIgnoreCase(profile)) {
                this.isDev = true;
                break;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy