From 13115331b8c0e53c2205080953a18714dfbfedbb Mon Sep 17 00:00:00 2001
From: Rujun Chen <rujche@microsoft.com>
Date: Tue, 15 Mar 2022 16:03:33 +0800
Subject: [PATCH] Re-design the configuration properties about key vault
 secrets (#27651)

---
 ...AzureKeyVaultPropertySourceProperties.java |   4 +-
 .../KeyVaultEnvironmentPostProcessor.java     | 221 ++++++-------
 .../environment/KeyVaultPropertySource.java   |  22 +-
 ...KeyVaultEnvironmentPostProcessorTests.java | 301 ++++++++++++------
 .../KeyVaultPropertySourceTests.java          |   2 +-
 .../util/AzurePropertiesUtils.java            |   6 +-
 6 files changed, 333 insertions(+), 223 deletions(-)

diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/keyvault/secrets/properties/AzureKeyVaultPropertySourceProperties.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/keyvault/secrets/properties/AzureKeyVaultPropertySourceProperties.java
index a7cb499bd208..310ebb228c56 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/keyvault/secrets/properties/AzureKeyVaultPropertySourceProperties.java
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/keyvault/secrets/properties/AzureKeyVaultPropertySourceProperties.java
@@ -9,8 +9,6 @@
 import java.time.Duration;
 import java.util.List;
 
-import static com.azure.spring.cloud.autoconfigure.keyvault.environment.KeyVaultPropertySource.DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME;
-
 /**
  * Configurations to set when Azure Key Vault is used as an external property source.
  *
@@ -31,7 +29,7 @@ public class AzureKeyVaultPropertySourceProperties extends AbstractAzureHttpConf
     /**
      * Name of this property source.
      */
-    private String name = DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME;
+    private String name;
     /**
      * Defines the constant for the property that enables/disables case-sensitive keys.
      */
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessor.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessor.java
index d17803830fdb..4172c6792719 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessor.java
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessor.java
@@ -4,16 +4,14 @@
 package com.azure.spring.cloud.autoconfigure.keyvault.environment;
 
 import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
 import com.azure.spring.cloud.autoconfigure.implementation.keyvault.secrets.properties.AzureKeyVaultPropertySourceProperties;
 import com.azure.spring.cloud.autoconfigure.implementation.keyvault.secrets.properties.AzureKeyVaultSecretProperties;
-import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.properties.utils.AzureGlobalPropertiesUtils;
 import com.azure.spring.cloud.core.implementation.util.AzurePropertiesUtils;
 import com.azure.spring.cloud.service.implementation.keyvault.secrets.SecretClientBuilderFactory;
 import org.apache.commons.logging.Log;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
-import org.springframework.boot.context.properties.PropertyMapper;
 import org.springframework.boot.context.properties.bind.Bindable;
 import org.springframework.boot.context.properties.bind.Binder;
 import org.springframework.boot.env.EnvironmentPostProcessor;
@@ -21,17 +19,17 @@
 import org.springframework.core.Ordered;
 import org.springframework.core.env.ConfigurableEnvironment;
 import org.springframework.core.env.MutablePropertySources;
-import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.StringUtils;
 
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.List;
 
 import static org.springframework.core.env.StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
 
 /**
- * Leverage {@link EnvironmentPostProcessor} to add Key Vault secrets as a property source.
+ * Leverage {@link EnvironmentPostProcessor} to insert {@link KeyVaultPropertySource}s into {@link ConfigurableEnvironment}.
+ * {@link KeyVaultPropertySource}s are constructed according to {@link AzureKeyVaultSecretProperties},
  *
  * @since 4.0.0
  */
@@ -51,115 +49,100 @@ public KeyVaultEnvironmentPostProcessor(Log logger) {
     }
 
     /**
-     * Construct a {@link KeyVaultEnvironmentPostProcessor} instance with default value.
+     * Construct a {@link KeyVaultEnvironmentPostProcessor} instance with a new {@link DeferredLog}.
      */
     public KeyVaultEnvironmentPostProcessor() {
         this.logger = new DeferredLog();
     }
 
     /**
-     * Post-process the environment.
-     *
-     * <p>
-     * Here we are going to process any key vault(s) and make them as available PropertySource(s). Note this supports
-     * both the singular key vault setup, as well as the multiple key vault setup.
-     * </p>
+     * Construct {@link KeyVaultPropertySource}s according to {@link AzureKeyVaultSecretProperties},
+     * then insert these {@link KeyVaultPropertySource}s into {@link ConfigurableEnvironment}.
      *
      * @param environment the environment.
      * @param application the application.
      */
     @Override
     public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
-        if (!isKeyVaultClientAvailable()) {
-            logger.info("Key Vault client is not present, skip the Key Vault property source");
+        if (!isKeyVaultClientOnClasspath()) {
+            logger.debug("Skip configuring Key Vault PropertySource. "
+                    + "Because com.azure:azure-security-keyvault-secrets doesn't exist in classpath.");
             return;
         }
 
-        final AzureKeyVaultSecretProperties keyVaultSecretProperties = loadProperties(Binder.get(environment));
-
-        // In propertySources list, smaller index has higher priority.
-        final List<AzureKeyVaultPropertySourceProperties> propertySources = keyVaultSecretProperties.getPropertySources();
-        Collections.reverse(propertySources);
-
-        if (propertySources.isEmpty() && StringUtils.hasText(keyVaultSecretProperties.getEndpoint())) {
-            propertySources.add(new AzureKeyVaultPropertySourceProperties());
+        final AzureKeyVaultSecretProperties secretProperties = loadProperties(environment);
+        if (!secretProperties.isPropertySourceEnabled()) {
+            logger.debug("Skip configuring Key Vault PropertySource. "
+                    + "Because spring.cloud.azure.keyvault.secret.property-source-enabled=false");
+            return;
+        }
+        if (secretProperties.getPropertySources().isEmpty()) {
+            logger.debug("Skip configuring Key Vault PropertySource. "
+                    + "Because spring.cloud.azure.keyvault.secret.property-sources is empty.");
+            return;
         }
 
-        if (isKeyVaultPropertySourceEnabled(keyVaultSecretProperties)) {
-            for (AzureKeyVaultPropertySourceProperties propertySource : propertySources) {
-                final AzureKeyVaultPropertySourceProperties properties = getMergeProperties(keyVaultSecretProperties,
-                                                                                            propertySource);
-                if (properties.isEnabled()) {
-                    addKeyVaultPropertySource(environment, properties);
-                }
+        final List<AzureKeyVaultPropertySourceProperties> propertiesList = secretProperties.getPropertySources();
+        List<KeyVaultPropertySource> keyVaultPropertySources = buildKeyVaultPropertySourceList(propertiesList);
+        final MutablePropertySources propertySources = environment.getPropertySources();
+        // reverse iterate order making sure smaller index has higher priority.
+        for (int i = keyVaultPropertySources.size() - 1; i >= 0; i--) {
+            KeyVaultPropertySource propertySource = keyVaultPropertySources.get(i);
+            logger.debug("Inserting Key Vault PropertySource. name = " + propertySource.getName());
+            if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
+                propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, propertySource);
+            } else {
+                propertySources.addFirst(propertySource);
             }
-        } else {
-            logger.debug("Key Vault 'propertySourceEnabled' or 'enabled' is not enabled");
         }
     }
 
-    // TODO (xiada) better way to implement this
-    private AzureKeyVaultPropertySourceProperties getMergeProperties(AzureKeyVaultSecretProperties secretProperties,
-                                                                     AzureKeyVaultPropertySourceProperties propertySource) {
-        AzureKeyVaultPropertySourceProperties mergedResult = new AzureKeyVaultPropertySourceProperties();
-        AzurePropertiesUtils.mergeAzureCommonProperties(secretProperties, propertySource, mergedResult);
-
-        mergedResult.setEndpoint(secretProperties.getEndpoint());
-        mergedResult.setServiceVersion(secretProperties.getServiceVersion());
-        mergedResult.setEnabled(propertySource.isEnabled());
-        mergedResult.setName(propertySource.getName());
-        mergedResult.setCaseSensitive(propertySource.isCaseSensitive());
-        mergedResult.setSecretKeys(propertySource.getSecretKeys());
-        mergedResult.setRefreshInterval(propertySource.getRefreshInterval());
-
-        PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
-        propertyMapper.from(propertySource.getEndpoint()).to(mergedResult::setEndpoint);
-        propertyMapper.from(propertySource.getServiceVersion()).to(mergedResult::setServiceVersion);
-
-        return mergedResult;
+    private List<KeyVaultPropertySource> buildKeyVaultPropertySourceList(
+            List<AzureKeyVaultPropertySourceProperties> propertiesList) {
+        List<KeyVaultPropertySource> propertySources = new ArrayList<>();
+        for (int i = 0; i < propertiesList.size(); i++) {
+            AzureKeyVaultPropertySourceProperties properties = propertiesList.get(i);
+            if (!properties.isEnabled()) {
+                logger.debug("Skip configuring Key Vault PropertySource. "
+                        + "Because spring.cloud.azure.keyvault.secret.property-sources[" + i + "].enabled = false.");
+                continue;
+            }
+            if (!StringUtils.hasText(properties.getEndpoint())) {
+                logger.debug("Skip configuring Key Vault PropertySource. "
+                        + "Because spring.cloud.azure.keyvault.secret.property-sources[" + i + "].endpoint is empty.");
+                continue;
+            }
+            propertySources.add(buildKeyVaultPropertySource(properties));
+        }
+        return propertySources;
     }
 
+    private KeyVaultPropertySource buildKeyVaultPropertySource(
+            AzureKeyVaultPropertySourceProperties properties) {
+        try {
+            final KeyVaultOperation keyVaultOperation = new KeyVaultOperation(
+                    buildSecretClient(properties),
+                    properties.getRefreshInterval(),
+                    properties.getSecretKeys(),
+                    properties.isCaseSensitive());
+            return new KeyVaultPropertySource(properties.getName(), keyVaultOperation);
+        } catch (final Exception exception) {
+            throw new IllegalStateException("Failed to configure KeyVault property source", exception);
+        }
+    }
 
-    /**
-     * Add a Key Vault property source.
-     *
-     * <p>
-     * The normalizedName is used to target a specific key vault (note if the name is the empty string it works as
-     * before with only one key vault present). The normalized name is the name of the specific key vault plus a
-     * trailing "." at the end.
-     * </p>
-     *
-     * @param environment The Spring environment.
-     * @param propertySource The property source properties.
-     * @throws IllegalStateException If KeyVaultOperations fails to initialize.
-     */
-    private void addKeyVaultPropertySource(ConfigurableEnvironment environment,
-                                          AzureKeyVaultPropertySourceProperties propertySource) {
-        Assert.notNull(propertySource.getEndpoint(), "endpoint must not be null!");
+    private SecretClient buildSecretClient(AzureKeyVaultPropertySourceProperties propertySourceProperties) {
+        AzureKeyVaultSecretProperties secretProperties = toAzureKeyVaultSecretProperties(propertySourceProperties);
+        return buildSecretClient(secretProperties);
+    }
 
+    private AzureKeyVaultSecretProperties toAzureKeyVaultSecretProperties(
+            AzureKeyVaultPropertySourceProperties propertySourceProperties) {
         AzureKeyVaultSecretProperties secretProperties = new AzureKeyVaultSecretProperties();
-        AzurePropertiesUtils.copyAzureCommonProperties(propertySource, secretProperties);
-        secretProperties.setServiceVersion(propertySource.getServiceVersion());
-        secretProperties.setEndpoint(propertySource.getEndpoint());
-        try {
-            final MutablePropertySources sources = environment.getPropertySources();
-            final SecretClient secretClient = buildSecretClient(secretProperties);
-            final KeyVaultOperation keyVaultOperation = new KeyVaultOperation(secretClient,
-                                                                              propertySource.getRefreshInterval(),
-                                                                              propertySource.getSecretKeys(),
-                                                                              propertySource.isCaseSensitive());
-            KeyVaultPropertySource keyVaultPropertySource = new KeyVaultPropertySource(propertySource.getName(),
-                                                                                       keyVaultOperation);
-            if (sources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
-                sources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, keyVaultPropertySource);
-            } else {
-                // TODO (xiada): confirm the order
-                sources.addFirst(keyVaultPropertySource);
-            }
-
-        } catch (final Exception ex) {
-            throw new IllegalStateException("Failed to configure KeyVault property source", ex);
-        }
+        AzurePropertiesUtils.copyAzureCommonProperties(propertySourceProperties, secretProperties);
+        secretProperties.setEndpoint(propertySourceProperties.getEndpoint());
+        secretProperties.setServiceVersion(propertySourceProperties.getServiceVersion());
+        return secretProperties;
     }
 
     /**
@@ -171,38 +154,60 @@ SecretClient buildSecretClient(AzureKeyVaultSecretProperties secretProperties) {
         return new SecretClientBuilderFactory(secretProperties).build().buildClient();
     }
 
-    private AzureKeyVaultSecretProperties loadProperties(Binder binder) {
-        AzureGlobalProperties azureProperties = binder
+    AzureKeyVaultSecretProperties loadProperties(ConfigurableEnvironment environment) {
+        Binder binder = Binder.get(environment);
+        AzureGlobalProperties globalProperties = binder
             .bind(AzureGlobalProperties.PREFIX, Bindable.of(AzureGlobalProperties.class))
             .orElseGet(AzureGlobalProperties::new);
 
-        AzureKeyVaultSecretProperties existingValue = new AzureKeyVaultSecretProperties();
-        AzureGlobalPropertiesUtils.loadProperties(azureProperties, existingValue);
+        AzureKeyVaultSecretProperties secretProperties = binder
+                .bind(AzureKeyVaultSecretProperties.PREFIX, Bindable.of(AzureKeyVaultSecretProperties.class))
+                .orElseGet(AzureKeyVaultSecretProperties::new);
+
+        List<AzureKeyVaultPropertySourceProperties> list = secretProperties.getPropertySources();
+
+        // Load properties from global properties.
+        for (int i = 0; i < list.size(); i++) {
+            list.set(i, buildMergedProperties(globalProperties, list.get(i)));
+        }
 
-        return binder
-            .bind(AzureKeyVaultSecretProperties.PREFIX,
-                  Bindable.of(AzureKeyVaultSecretProperties.class).withExistingValue(existingValue))
-            .orElseGet(AzureKeyVaultSecretProperties::new);
+        // Name must be unique for each property source.
+        // Because MutablePropertySources#add will remove property source with existing name.
+        for (int i = 0; i < list.size(); i++) {
+            AzureKeyVaultPropertySourceProperties propertySourceProperties = list.get(i);
+            if (!StringUtils.hasText(propertySourceProperties.getName())) {
+                propertySourceProperties.setName(buildPropertySourceName(i));
+            }
+        }
+        return secretProperties;
     }
 
-    /**
-     * Is the Key Vault property source enabled.
-     *
-     * @param properties The Azure Key Vault Secret properties.
-     * @return true if the key vault is enabled, false otherwise.
-     */
-    private boolean isKeyVaultPropertySourceEnabled(AzureKeyVaultSecretProperties properties) {
-        return properties.isEnabled()
-            && (properties.isPropertySourceEnabled() && !properties.getPropertySources().isEmpty());
+    private AzureKeyVaultPropertySourceProperties buildMergedProperties(
+            AzureGlobalProperties globalProperties,
+            AzureKeyVaultPropertySourceProperties propertySourceProperties) {
+        AzureKeyVaultPropertySourceProperties mergedProperties = new AzureKeyVaultPropertySourceProperties();
+        AzurePropertiesUtils.mergeAzureCommonProperties(globalProperties, propertySourceProperties, mergedProperties);
+        mergedProperties.setEnabled(propertySourceProperties.isEnabled());
+        mergedProperties.setName(propertySourceProperties.getName());
+        mergedProperties.setEndpoint(propertySourceProperties.getEndpoint());
+        mergedProperties.setServiceVersion(propertySourceProperties.getServiceVersion());
+        mergedProperties.setCaseSensitive(propertySourceProperties.isCaseSensitive());
+        mergedProperties.setSecretKeys(propertySourceProperties.getSecretKeys());
+        mergedProperties.setRefreshInterval(propertySourceProperties.getRefreshInterval());
+        return mergedProperties;
+    }
+
+    String buildPropertySourceName(int index) {
+        return "azure-key-vault-secret-property-source-" + index;
     }
 
-    private boolean isKeyVaultClientAvailable() {
+    private boolean isKeyVaultClientOnClasspath() {
         return ClassUtils.isPresent("com.azure.security.keyvault.secrets.SecretClient",
                                     KeyVaultEnvironmentPostProcessor.class.getClassLoader());
     }
 
     /**
-     *
+     * Get the order value of this object.
      * @return The order value.
      */
     @Override
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySource.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySource.java
index 9ad7ba1e3a1f..897eb313899f 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySource.java
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySource.java
@@ -14,26 +14,14 @@
 public class KeyVaultPropertySource extends EnumerablePropertySource<KeyVaultOperation> {
 
     private final KeyVaultOperation operations;
-    public static final String DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME = "azurekv";
 
     /**
-     * Creates a new instance of {@link KeyVaultPropertySource}.
-     *
-     * @param keyVaultName the KeyVault name
-     * @param operation the KeyVault operation
+     * Create a new {@code KeyVaultPropertySource} with the given name and {@link KeyVaultOperation}.
+     * @param name the associated name
+     * @param operation the {@link KeyVaultOperation}
      */
-    public KeyVaultPropertySource(String keyVaultName, KeyVaultOperation operation) {
-        super(keyVaultName, operation);
-        this.operations = operation;
-    }
-
-    /**
-     * Creates a new instance of {@link KeyVaultPropertySource}.
-     *
-     * @param operation the KeyVault operation
-     */
-    public KeyVaultPropertySource(KeyVaultOperation operation) {
-        super(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME, operation);
+    public KeyVaultPropertySource(String name, KeyVaultOperation operation) {
+        super(name, operation);
         this.operations = operation;
     }
 
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessorTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessorTests.java
index b0d9db936142..ae078843ba3b 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessorTests.java
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultEnvironmentPostProcessorTests.java
@@ -4,8 +4,10 @@
 package com.azure.spring.cloud.autoconfigure.keyvault.environment;
 
 import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.spring.cloud.autoconfigure.implementation.keyvault.secrets.properties.AzureKeyVaultPropertySourceProperties;
 import com.azure.spring.cloud.autoconfigure.implementation.keyvault.secrets.properties.AzureKeyVaultSecretProperties;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 import org.springframework.boot.SpringApplication;
@@ -19,9 +21,9 @@
 import java.util.Collections;
 import java.util.Iterator;
 
-import static com.azure.spring.cloud.autoconfigure.keyvault.environment.KeyVaultPropertySource.DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
@@ -32,143 +34,256 @@
 
 class KeyVaultEnvironmentPostProcessorTests {
 
+    private static final String NAME_0 = "name_0";
+    private static final String NAME_1 = "name_1";
+    private static final String ENDPOINT_0 = "https://test0.vault.azure.net/";
+    private static final String ENDPOINT_1 = "https://test1.vault.azure.net/";
+
     private final SpringApplication application = new SpringApplication();
     private KeyVaultEnvironmentPostProcessor processor;
     private MockEnvironment environment;
     private MutablePropertySources propertySources;
 
     @BeforeEach
-    void setup() {
+    void beforeEach() {
         processor = spy(new KeyVaultEnvironmentPostProcessor(new DeferredLog()));
         environment = new MockEnvironment();
         propertySources = environment.getPropertySources();
+        SecretClient secretClient = mock(SecretClient.class);
+        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
     }
 
     @Test
     void postProcessorHasConfiguredOrder() {
-        final KeyVaultEnvironmentPostProcessor processor = new KeyVaultEnvironmentPostProcessor(new DeferredLog());
+        final KeyVaultEnvironmentPostProcessor processor = new KeyVaultEnvironmentPostProcessor();
         assertEquals(processor.getOrder(), KeyVaultEnvironmentPostProcessor.ORDER);
     }
 
     @Test
-    void keyVaultClientIsNotAvailable() {
+    void insertSinglePropertySourceTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        processor.postProcessEnvironment(environment, application);
+        assertTrue(propertySources.contains(NAME_0));
+    }
+
+    @Test
+    void insertMultiplePropertySourceTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertTrue(propertySources.contains(NAME_0));
+        assertTrue(propertySources.contains(NAME_1));
+    }
+
+    @Test
+    void keyVaultClientNotExistInClassPathTest() {
         try (MockedStatic<ClassUtils> classUtils = mockStatic(ClassUtils.class)) {
-            classUtils.when(() ->
-                          ClassUtils.isPresent("com.azure.security.keyvault.secrets.SecretClient",
-                              this.getClass().getClassLoader()))
-                      .thenReturn(false);
-            processor.postProcessEnvironment(this.environment, this.application);
-            final MutablePropertySources sources = this.environment.getPropertySources();
-            assertFalse(sources.contains(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME));
+            classUtils.when(() -> ClassUtils.isPresent("com.azure.security.keyvault.secrets.SecretClient", getClass().getClassLoader()))
+                    .thenReturn(false);
+            environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+            environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+            environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+            environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+            processor.postProcessEnvironment(environment, application);
+            assertFalse(propertySources.contains(NAME_0));
         }
     }
 
     @Test
-    void sourcesNotExistsWhenConfigureEnabledFalse() {
-        environment.setProperty("spring.cloud.azure.keyvault.secret.enabled", "false");
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        assertFalse(sources.contains(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME));
+    void disableAllPropertySourceTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "false");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertFalse(propertySources.contains(NAME_0));
+        assertFalse(propertySources.contains(NAME_1));
     }
 
     @Test
-    void sourcesNotExistsWhenConfigurePropertySourceEnabledFalseAndPropertySourcesEmpty() {
-        environment.setProperty("spring.cloud.azure.keyvault.secret.propertySourceEnabled", "false");
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        assertFalse(sources.contains(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME));
+    void emptyPropertySourceListTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        processor.postProcessEnvironment(environment, application);
+        assertEquals(1, propertySources.size());
     }
 
     @Test
-    void defaultPropertySourcesAdded() {
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint",
-            "https://test.vault.azure.net/");
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        assertTrue(sources.contains(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME));
-    }
-
-    @Test
-    void configuredPropertySourcesAddedWhenEnabled() {
-        String propertySourcesOne = "testkeyOne";
-        String propertySourcesTwo = "testkeyTwo";
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", propertySourcesOne);
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint",
-            "https://test.vault.azure.net/");
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "false");
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", propertySourcesTwo);
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint",
-            "https://test2.vault.azure.net/");
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        assertTrue(sources.contains(propertySourcesOne));
-        assertFalse(sources.contains(propertySourcesTwo));
+    void disableSpecificOnePropertySourceTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "false");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertFalse(propertySources.contains(NAME_0));
+        assertTrue(propertySources.contains(NAME_1));
     }
 
     @Test
-    void configuredPropertySourcesIsFirst() {
-        String sourceName = "testkey";
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", sourceName);
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint",
-            "https://test.vault.azure.net/");
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        Iterator<PropertySource<?>> iterator = sources.iterator();
-        assertTrue(iterator.next().getName().equals(sourceName));
+    void enableByDefaultTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertTrue(propertySources.contains(NAME_0));
+        assertTrue(propertySources.contains(NAME_1));
     }
 
     @Test
-    void configuredPropertySourcesWithEndpoint() {
-        String mockEndPoint = "https://mockendpoint.vault.azure.net";
-        environment.setProperty("spring.cloud.azure.keyvault.secret.endpoint", mockEndPoint);
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        Iterator<PropertySource<?>> iterator = sources.iterator();
-        assertTrue(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME.equals(iterator.next().getName()));
+    void endPointNotConfiguredTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertFalse(propertySources.contains(NAME_0));
+        assertTrue(propertySources.contains(NAME_1));
     }
 
     @Test
-    void configuredPropertySourcesIgnoreEndPoint() {
-        String mockEndPoint = "https://mockendpoint.vault.azure.net";
-        environment.setProperty("spring.cloud.azure.keyvault.secret.endpoint", mockEndPoint);
+    void defaultPropertySourceNameTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        assertTrue(propertySources.contains(processor.buildPropertySourceName(0)));
+        assertTrue(propertySources.contains(processor.buildPropertySourceName(1)));
+    }
 
-        String sourceName = "testkey";
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", sourceName);
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint",
-            "https://test.vault.azure.net/");
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        Iterator<PropertySource<?>> iterator = sources.iterator();
-        assertTrue(iterator.next().getName().equals(sourceName));
+    @Test
+    void keyVaultPropertySourceHasHighestPriorityIfEnvironmentPropertySourceNotExistTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        processor.postProcessEnvironment(environment, application);
+        Iterator<PropertySource<?>> iterator = propertySources.iterator();
+        assertEquals(NAME_0, iterator.next().getName());
+        assertTrue(iterator.hasNext());
     }
 
     @Test
-    void configuredPropertySourcesLocationIsAfterSystemEnvironmentPropertySources() {
-        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint",
-            "https://test.vault.azure.net/");
-        SecretClient secretClient = mock(SecretClient.class);
-        doReturn(secretClient).when(processor).buildSecretClient(any(AzureKeyVaultSecretProperties.class));
-        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, Collections.emptyMap()));
-        processor.postProcessEnvironment(this.environment, this.application);
-        final MutablePropertySources sources = this.environment.getPropertySources();
-        Iterator<PropertySource<?>> it = sources.iterator();
-        while (it.hasNext()) {
-            PropertySource<?> propertySource = it.next();
+    void keyVaultPropertySourceHasLowerPriorityThanEnvironmentPropertySourceTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        propertySources.addFirst(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, Collections.emptyMap()));
+        processor.postProcessEnvironment(environment, application);
+        Iterator<PropertySource<?>> iterator = propertySources.iterator();
+        while (iterator.hasNext()) {
+            PropertySource<?> propertySource = iterator.next();
             if (SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME.equals(propertySource.getName())) {
                 break;
             }
         }
-        assertTrue(DEFAULT_AZURE_KEYVAULT_PROPERTYSOURCE_NAME.equals(it.next().getName()));
+        assertEquals(NAME_0, iterator.next().getName());
+        assertTrue(iterator.hasNext());
+    }
+
+    @Test
+    void keyVaultPropertySourceOrderTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].name", NAME_1);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[1].endpoint", ENDPOINT_1);
+        processor.postProcessEnvironment(environment, application);
+        Iterator<PropertySource<?>> iterator = propertySources.iterator();
+        assertEquals(NAME_0, iterator.next().getName());
+        assertEquals(NAME_1, iterator.next().getName());
+        assertTrue(iterator.hasNext());
+    }
+
+    @Test
+    void globalPropertiesTakeEffectIfSpecificPropertiesNotSetTest() {
+        final String globalHostname = "globalHostname";
+        final String globalApplicationId = "globalApplicationId";
+        final String globalTenantId = "globalTenantId";
+        final String globalUsername = "globalUsername";
+        final int globalMaxRetries = 1;
+        environment.setProperty("spring.cloud.azure.client.application-id", globalApplicationId);
+        environment.setProperty("spring.cloud.azure.credential.username", globalUsername);
+        environment.setProperty("spring.cloud.azure.profile.tenant-id", globalTenantId);
+        environment.setProperty("spring.cloud.azure.proxy.hostname", globalHostname);
+        environment.setProperty("spring.cloud.azure.retry.fixed.max-retries", "" + globalMaxRetries);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        AzureKeyVaultSecretProperties secretProperties = processor.loadProperties(environment);
+        AzureKeyVaultPropertySourceProperties properties = secretProperties.getPropertySources().get(0);
+        assertEquals(globalUsername, properties.getCredential().getUsername());
+        assertEquals(globalApplicationId, properties.getClient().getApplicationId());
+        assertEquals(globalTenantId, properties.getProfile().getTenantId());
+        assertEquals(globalHostname, properties.getProxy().getHostname());
+        assertEquals(globalMaxRetries, properties.getRetry().getFixed().getMaxRetries());
+    }
+
+    @Test
+    void specificPropertiesHasHigherPriorityThanGlobalPropertiesTest() {
+        final String globalHostname = "globalHostname";
+        final String globalApplicationId = "globalApplicationId";
+        final String globalTenantId = "globalTenantId";
+        final String globalUsername = "globalUsername";
+        final int globalMaxRetries = 1;
+        final String specificHostname = "specificHostname";
+        final String specificApplicationId = "specificApplicationId";
+        final String specificTenantId = "specificTenantId";
+        final String specificUsername = "specificUsername";
+        final int specificMaxRetries = 2;
+        environment.setProperty("spring.cloud.azure.client.application-id", globalApplicationId);
+        environment.setProperty("spring.cloud.azure.credential.username", globalUsername);
+        environment.setProperty("spring.cloud.azure.profile.tenant-id", globalTenantId);
+        environment.setProperty("spring.cloud.azure.proxy.hostname", globalHostname);
+        environment.setProperty("spring.cloud.azure.retry.fixed.max-retries", "" + globalMaxRetries);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].client.application-id", specificApplicationId);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].credential.username", specificUsername);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].profile.tenant-id", specificTenantId);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].proxy.hostname", specificHostname);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].retry.fixed.max-retries", "" + specificMaxRetries);
+        AzureKeyVaultSecretProperties secretProperties = processor.loadProperties(environment);
+        AzureKeyVaultPropertySourceProperties properties = secretProperties.getPropertySources().get(0);
+        assertEquals(specificUsername, properties.getCredential().getUsername());
+        assertEquals(specificApplicationId, properties.getClient().getApplicationId());
+        assertEquals(specificTenantId, properties.getProfile().getTenantId());
+        assertEquals(specificHostname, properties.getProxy().getHostname());
+        assertEquals(specificMaxRetries, properties.getRetry().getFixed().getMaxRetries());
+    }
+
+    @Disabled("Disable it to unblock Azure Dev Ops pipeline: https://dev.azure.com/azure-sdk/public/_build/results?buildId=1434354&view=logs&j=c1fb1ddd-7688-52ac-4c5f-1467e51181f3")
+    @Test
+    void buildKeyVaultPropertySourceWithExceptionTest() {
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-source-enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].enabled", "true");
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].name", NAME_0);
+        environment.setProperty("spring.cloud.azure.keyvault.secret.property-sources[0].endpoint", ENDPOINT_0);
+        assertThrows(IllegalStateException.class,
+                () -> new KeyVaultEnvironmentPostProcessor().postProcessEnvironment(environment, application));
     }
 }
 
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySourceTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySourceTests.java
index 9acc9c4cc7b1..19eee73e47cd 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySourceTests.java
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/keyvault/environment/KeyVaultPropertySourceTests.java
@@ -35,7 +35,7 @@ public void setup() {
         when(keyVaultOperation.getProperty(anyString())).thenReturn(TEST_PROPERTY_NAME_1);
         when(keyVaultOperation.getPropertyNames()).thenReturn(propertyNameList);
 
-        keyVaultPropertySource = new KeyVaultPropertySource(keyVaultOperation);
+        keyVaultPropertySource = new KeyVaultPropertySource("azure-key-vault-secret-property-source", keyVaultOperation);
     }
 
     @AfterEach
diff --git a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePropertiesUtils.java b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePropertiesUtils.java
index c3b94eb4b70c..9b6ddc09d00b 100644
--- a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePropertiesUtils.java
+++ b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePropertiesUtils.java
@@ -69,7 +69,11 @@ public static <T extends AzureProperties> void copyAzureCommonPropertiesIgnoreNu
         copyPropertiesIgnoreNull(source.getCredential(), target.getCredential());
 
         if (source instanceof RetryOptionsProvider && target instanceof RetryOptionsProvider) {
-            copyPropertiesIgnoreNull(((RetryOptionsProvider) source).getRetry(), ((RetryOptionsProvider) target).getRetry());
+            RetryOptionsProvider.RetryOptions sourceRetry = ((RetryOptionsProvider) source).getRetry();
+            RetryOptionsProvider.RetryOptions targetRetry = ((RetryOptionsProvider) target).getRetry();
+            copyPropertiesIgnoreNull(sourceRetry, targetRetry);
+            copyPropertiesIgnoreNull(sourceRetry.getExponential(), targetRetry.getExponential());
+            copyPropertiesIgnoreNull(sourceRetry.getFixed(), targetRetry.getFixed());
         }
     }