From 8dd4aaed48c49ec4c54f1d98a4e00084a3400297 Mon Sep 17 00:00:00 2001
From: Matthew Metcalf
Date: Mon, 16 Dec 2024 11:20:59 -0800
Subject: [PATCH 01/13] Spring Feature Management - RemoveV1Schema (#43405)
* RemoveV1Schema
* Removed targeting change
* extra java doc removed
* Review comments
* Update FeatureManagementException.java
---
.../feature/management/FeatureManager.java | 67 ++-----
.../implementation/FeatureFilterUtils.java | 8 +-
.../FeatureManagementProperties.java | 185 +-----------------
.../implementation/models/Feature.java | 87 --------
.../targeting/TargetingFilterSettings.java | 26 ---
.../models/Conditions.java | 31 +--
.../Feature.java} | 19 +-
.../FeatureFilterEvaluationContext.java | 18 +-
...ntSideFeatureManagementPropertiesTest.java | 44 -----
.../FeatureManagementConfigurationTest.java | 38 ++++
.../FeatureManagementTestConfigurations.java | 23 +++
.../management/FeatureManagerTest.java | 143 +++++---------
...deFeatureManagementPropertiesListTest.java | 47 -----
...erSideFeatureManagementPropertiesTest.java | 48 -----
.../filters/TargetingFilterTest.java | 2 +-
.../implementation/TestConfiguration.java | 18 --
.../src/test/resources/application.yaml | 16 +-
17 files changed, 190 insertions(+), 630 deletions(-)
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/models/Feature.java
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/targeting/TargetingFilterSettings.java
rename sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/{implementation => }/models/Conditions.java (56%)
rename sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/{implementation/models/ServerSideFeature.java => models/Feature.java} (77%)
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ClientSideFeatureManagementPropertiesTest.java
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagementConfigurationTest.java
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagementTestConfigurations.java
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ServerSideFeatureManagementPropertiesListTest.java
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ServerSideFeatureManagementPropertiesTest.java
delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/implementation/TestConfiguration.java
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
index 04be3548066f..9574a65703e0 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
@@ -3,7 +3,6 @@
package com.azure.spring.cloud.feature.management;
import java.util.HashSet;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
@@ -17,7 +16,7 @@
import com.azure.spring.cloud.feature.management.filters.FeatureFilter;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
-import com.azure.spring.cloud.feature.management.implementation.models.Feature;
+import com.azure.spring.cloud.feature.management.models.Feature;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.FilterNotFoundException;
@@ -51,9 +50,9 @@ public class FeatureManager {
}
/**
- * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter
- * returns true it returns true. If no filter returns true, it returns false. If there are no
- * filters, it returns true. If feature isn't found it returns false.
+ * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
+ * isn't found it returns false.
*
* @param feature Feature being checked.
* @return state of the feature
@@ -64,9 +63,9 @@ public Mono isEnabledAsync(String feature) {
}
/**
- * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter
- * returns true it returns true. If no filter returns true, it returns false. If there are no
- * filters, it returns true. If feature isn't found it returns false.
+ * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
+ * isn't found it returns false.
*
* @param feature Feature being checked.
* @return state of the feature
@@ -76,34 +75,28 @@ public Boolean isEnabled(String feature) throws FilterNotFoundException {
return checkFeature(feature);
}
- private boolean checkFeature(String feature) throws FilterNotFoundException {
- if (featureManagementConfigurations.getFeatureManagement() == null
- || featureManagementConfigurations.getOnOff() == null) {
- return false;
- }
-
- Boolean boolFeature = featureManagementConfigurations.getOnOff().get(feature);
-
- if (boolFeature != null) {
- return boolFeature;
- }
+ private boolean checkFeature(String featureName) throws FilterNotFoundException {
+ Feature featureFlag = featureManagementConfigurations.getFeatureFlags().stream()
+ .filter(feature -> feature.getId().equals(featureName)).findAny().orElse(null);
- Feature featureItem = featureManagementConfigurations.getFeatureManagement().get(feature);
-
- if (featureItem == null || !featureItem.getEvaluate()) {
+ if (featureFlag == null) {
return false;
}
- Stream filters = featureItem.getEnabledFor().values().stream()
+ Stream filters = featureFlag.getConditions().getClientFilters().stream()
.filter(Objects::nonNull).filter(featureFilter -> featureFilter.getName() != null);
+
+ if (featureFlag.getConditions().getClientFilters().size() == 0) {
+ return featureFlag.isEnabled();
+ }
// All Filters must be true
- if (featureItem.getRequirementType().equals("All")) {
- return filters.allMatch(featureFilter -> isFeatureOn(featureFilter, feature));
+ if (featureFlag.getConditions().getRequirementType().equals("All")) {
+ return filters.allMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
}
// Any Filter must be true
- return filters.anyMatch(featureFilter -> isFeatureOn(featureFilter, feature));
+ return filters.anyMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
}
private boolean isFeatureOn(FeatureFilterEvaluationContext filter, String feature) {
@@ -129,25 +122,7 @@ private boolean isFeatureOn(FeatureFilterEvaluationContext filter, String featur
* @return a set of all feature names
*/
public Set getAllFeatureNames() {
- Set allFeatures = new HashSet<>();
-
- allFeatures.addAll(featureManagementConfigurations.getOnOff().keySet());
- allFeatures.addAll(featureManagementConfigurations.getFeatureManagement().keySet());
- return allFeatures;
+ return new HashSet(
+ featureManagementConfigurations.getFeatureFlags().stream().map(feature -> feature.getId()).toList());
}
-
- /**
- * @return the featureManagement
- */
- Map getFeatureManagement() {
- return featureManagementConfigurations.getFeatureManagement();
- }
-
- /**
- * @return the onOff
- */
- Map getOnOff() {
- return featureManagementConfigurations.getOnOff();
- }
-
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
index e95dc6a53404..a8631bfc9366 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
@@ -3,12 +3,13 @@
package com.azure.spring.cloud.feature.management.implementation;
-import org.springframework.util.StringUtils;
-
import java.util.Collection;
import java.util.Map;
+import org.springframework.util.StringUtils;
+
public class FeatureFilterUtils {
+
/**
* Looks at the given key in the parameters and coverts it to a list if it is currently a map.
*
@@ -21,6 +22,8 @@ public static void updateValueFromMapToList(Map parameters, Stri
if (objectMap instanceof Map) {
Collection
*
* @param trim prefix to trim
+ * @param isRefresh true if a refresh triggered the loading of the Snapshot.
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
- public void initProperties(List trim) throws InvalidConfigurationPropertyValueException {
- processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName), null, trim);
+ public void initProperties(List trim, boolean isRefresh) throws InvalidConfigurationPropertyValueException {
+ processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, isRefresh), null, trim);
FeatureFlags featureFlags = new FeatureFlags(null, featureFlagsList);
featureFlagClient.proccessFeatureFlags(featureFlags, replicaClient.getEndpoint());
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigBoostrapRegistrar.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigBoostrapRegistrar.java
new file mode 100644
index 000000000000..be7d90bb5e06
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigBoostrapRegistrar.java
@@ -0,0 +1,136 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.appconfiguration.config.implementation;
+
+import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
+import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.util.StringUtils;
+
+import com.azure.data.appconfiguration.ConfigurationClientBuilder;
+import com.azure.spring.cloud.appconfiguration.config.ConfigurationClientCustomizer;
+import com.azure.spring.cloud.appconfiguration.config.KeyVaultSecretProvider;
+import com.azure.spring.cloud.appconfiguration.config.SecretClientCustomizer;
+import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
+import com.azure.spring.cloud.autoconfigure.implementation.appconfiguration.AzureAppConfigurationProperties;
+import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
+import com.azure.spring.cloud.autoconfigure.implementation.properties.core.AbstractAzureHttpConfigurationProperties;
+import com.azure.spring.cloud.autoconfigure.implementation.properties.core.authentication.TokenCredentialConfigurationProperties;
+import com.azure.spring.cloud.autoconfigure.implementation.properties.utils.AzureGlobalPropertiesUtils;
+import com.azure.spring.cloud.core.customizer.AzureServiceClientBuilderCustomizer;
+import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier;
+import com.azure.spring.cloud.service.implementation.appconfiguration.ConfigurationClientBuilderFactory;
+import com.azure.spring.cloud.service.implementation.keyvault.secrets.SecretClientBuilderFactory;
+
+@EnableConfigurationProperties(AppConfigurationProviderProperties.class)
+class AzureAppConfigurationBootstrapRegistrar {
+
+ static void register(ConfigDataLocationResolverContext context, Binder binder,
+ AppConfigurationProperties properties, AppConfigurationProviderProperties appProperties,
+ ReplicaLookUp replicaLookup) {
+
+ AzureGlobalProperties globalProperties = binder
+ .bind(AzureGlobalProperties.PREFIX, Bindable.of(AzureGlobalProperties.class))
+ .orElseGet(AzureGlobalProperties::new);
+ AzureAppConfigurationProperties appConfigurationProperties = binder
+ .bind(AzureAppConfigurationProperties.PREFIX, Bindable.of(AzureAppConfigurationProperties.class))
+ .orElseGet(AzureAppConfigurationProperties::new);
+ // the properties are used to custom the ConfigurationClientBuilder
+ AzureAppConfigurationProperties loadedProperties = AzureGlobalPropertiesUtils.loadProperties(globalProperties,
+ appConfigurationProperties);
+
+ boolean isCredentialConfigured = isCredentialConfigured(loadedProperties);
+
+ AppConfigurationKeyVaultClientFactory keyVaultClientFactory = appConfigurationKeyVaultClientFactory(context,
+ isCredentialConfigured, appProperties.getMaxRetryTime());
+ AppConfigurationReplicaClientsBuilder replicaClientsBuilder = replicaClientBuilder(context, binder,
+ keyVaultClientFactory, loadedProperties, isCredentialConfigured, appProperties.getMaxRetries());
+
+ context.getBootstrapContext().registerIfAbsent(AppConfigurationKeyVaultClientFactory.class,
+ InstanceSupplier.from(() -> keyVaultClientFactory));
+ context.getBootstrapContext().registerIfAbsent(AppConfigurationReplicaClientFactory.class,
+ InstanceSupplier.from(() -> buildClientFactory(replicaClientsBuilder, properties, replicaLookup)));
+ }
+
+ private static AppConfigurationKeyVaultClientFactory appConfigurationKeyVaultClientFactory(
+ ConfigDataLocationResolverContext context, boolean isCredentialConfigured, Integer maxRetryTime)
+ throws IllegalArgumentException {
+
+ SecretClientCustomizer customizer = context.getBootstrapContext().getOrElse(SecretClientCustomizer.class, null);
+ KeyVaultSecretProvider secretProvider = context.getBootstrapContext().getOrElse(KeyVaultSecretProvider.class,
+ null);
+ SecretClientBuilderFactory secretClientFactory = context.getBootstrapContext()
+ .getOrElse(SecretClientBuilderFactory.class, null);
+
+ return new AppConfigurationKeyVaultClientFactory(customizer, secretProvider, secretClientFactory,
+ isCredentialConfigured, maxRetryTime);
+ }
+
+ private static AppConfigurationReplicaClientFactory buildClientFactory(
+ AppConfigurationReplicaClientsBuilder clientBuilder, AppConfigurationProperties properties,
+ ReplicaLookUp replicaLookup) {
+ return new AppConfigurationReplicaClientFactory(clientBuilder, properties.getStores(), replicaLookup);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static AppConfigurationReplicaClientsBuilder replicaClientBuilder(ConfigDataLocationResolverContext context,
+ Binder binder, AppConfigurationKeyVaultClientFactory keyVaultClientFactory,
+ AzureAppConfigurationProperties properties, boolean isCredentialConfigured, Integer maxRetries) {
+
+ InstanceSupplier> customizer = context
+ .getBootstrapContext()
+ .getRegisteredInstanceSupplier(
+ (Class>) (Class>) AzureServiceClientBuilderCustomizer.class);
+ ConfigurationClientBuilderFactory clientFactory = context.getBootstrapContext()
+ .getOrElseSupply(ConfigurationClientBuilderFactory.class, () -> {
+ ConfigurationClientBuilderFactory factory = new ConfigurationClientBuilderFactory(properties);
+ factory.setSpringIdentifier(AzureSpringIdentifier.AZURE_SPRING_APP_CONFIG);
+ if (customizer != null) {
+ factory.addBuilderCustomizer(customizer.get(context.getBootstrapContext()));
+ }
+ return factory;
+ });
+ if (customizer != null) {
+ clientFactory.addBuilderCustomizer(customizer.get(context.getBootstrapContext()));
+ }
+
+ InstanceSupplier configurationClientCustomizer = context
+ .getBootstrapContext()
+ .getRegisteredInstanceSupplier(
+ (Class) (Class>) ConfigurationClientCustomizer.class);
+
+ ConfigurationClientCustomizer clientCustomizer = null;
+ if (configurationClientCustomizer != null) {
+ clientCustomizer = configurationClientCustomizer.get(context.getBootstrapContext());
+ }
+
+ return new AppConfigurationReplicaClientsBuilder(maxRetries, clientFactory, clientCustomizer,
+ isCredentialConfigured, keyVaultClientFactory.isConfigured());
+ }
+
+ private static boolean isCredentialConfigured(AbstractAzureHttpConfigurationProperties properties) {
+ if (properties.getCredential() != null) {
+ TokenCredentialConfigurationProperties tokenProps = properties.getCredential();
+ if (StringUtils.hasText(tokenProps.getClientCertificatePassword())) {
+ return true;
+ } else if (StringUtils.hasText(tokenProps.getClientCertificatePath())) {
+ return true;
+ } else if (StringUtils.hasText(tokenProps.getClientId())) {
+ return true;
+ } else if (StringUtils.hasText(tokenProps.getClientSecret())) {
+ return true;
+ } else if (StringUtils.hasText(tokenProps.getUsername())) {
+ return true;
+ } else if (StringUtils.hasText(tokenProps.getPassword())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java
new file mode 100644
index 000000000000..72c1e03c3a3f
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java
@@ -0,0 +1,199 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.appconfiguration.config.implementation;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
+import org.springframework.boot.context.config.ConfigData;
+import org.springframework.boot.context.config.ConfigDataLoader;
+import org.springframework.boot.context.config.ConfigDataLoaderContext;
+import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.boot.logging.DeferredLogFactory;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.util.StringUtils;
+
+import com.azure.data.appconfiguration.models.ConfigurationSetting;
+import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagKeyValueSelector;
+
+@EnableConfigurationProperties(AppConfigurationProviderProperties.class)
+public class AzureAppConfigDataLoader implements ConfigDataLoader {
+
+ private static Log logger = new DeferredLog();
+
+ private AzureAppConfigDataResource resource;
+
+ private AppConfigurationReplicaClientFactory replicaClientFactory;
+
+ private AppConfigurationKeyVaultClientFactory keyVaultClientFactory;
+
+ private StateHolder storeState = new StateHolder();
+
+ private FeatureFlagClient featureFlagClient;
+
+ public AzureAppConfigDataLoader(DeferredLogFactory logFactory) {
+ logger = logFactory.getLog(getClass());
+ }
+
+ @Override
+ public ConfigData load(ConfigDataLoaderContext context, AzureAppConfigDataResource resource)
+ throws IOException, ConfigDataResourceNotFoundException {
+ this.resource = resource;
+ storeState.setNextForcedRefresh(resource.getRefreshInterval());
+
+ if (context.getBootstrapContext().isRegistered(FeatureFlagClient.class)) {
+ this.featureFlagClient = context.getBootstrapContext().get(FeatureFlagClient.class);
+ } else {
+ this.featureFlagClient = new FeatureFlagClient();
+ context.getBootstrapContext().registerIfAbsent(FeatureFlagClient.class,
+ InstanceSupplier.from(() -> this.featureFlagClient));
+ }
+
+ List> sourceList = new ArrayList<>();
+
+ if (resource.isConfigStoreEnabled()) {
+ replicaClientFactory = context.getBootstrapContext()
+ .get(AppConfigurationReplicaClientFactory.class);
+ keyVaultClientFactory = context.getBootstrapContext()
+ .get(AppConfigurationKeyVaultClientFactory.class);
+
+ List clients = replicaClientFactory
+ .getAvailableClients(resource.getEndpoint(), true);
+
+ boolean reloadFailed = false;
+
+ // Feature Management needs to be set in the last config store.
+
+ for (AppConfigurationReplicaClient client : clients) {
+ sourceList = new ArrayList<>();
+
+ if (reloadFailed
+ && !AppConfigurationRefreshUtil.refreshStoreCheck(client,
+ replicaClientFactory.findOriginForEndpoint(client.getEndpoint()))) {
+ // This store doesn't have any changes where to refresh store did. Skipping Checking next.
+ continue;
+ }
+
+ // Reverse in order to add Profile specific properties earlier, and last profile comes first
+ try {
+ sourceList.addAll(createSettings(client));
+ List featureFlags = createFeatureFlags(client);
+
+ logger.debug("PropertySource context.");
+ AppConfigurationStoreMonitoring monitoring = resource.getMonitoring();
+
+ storeState.setStateFeatureFlag(resource.getEndpoint(), featureFlags,
+ monitoring.getFeatureFlagRefreshInterval());
+
+ if (monitoring.isEnabled()) {
+ // Setting new ETag values for Watch
+ List watchKeysSettings = monitoring.getTriggers().stream()
+ .map(trigger -> client.getWatchKey(trigger.getKey(), trigger.getLabel(),
+ resource.isRefresh()))
+ .toList();
+
+ storeState.setState(resource.getEndpoint(), watchKeysSettings, monitoring.getRefreshInterval());
+ }
+ storeState.setLoadState(resource.getEndpoint(), true);
+ } catch (AppConfigurationStatusException e) {
+ reloadFailed = true;
+ replicaClientFactory.backoffClientClient(resource.getEndpoint(), client.getEndpoint());
+ } catch (Exception e) {
+ failedToGeneratePropertySource(e);
+
+ // Not a retiable error
+ break;
+ }
+ if (sourceList.size() > 0) {
+ break;
+ }
+ }
+ }
+
+ StateHolder.updateState(storeState);
+ sourceList.add(new AppConfigurationFeatureManagementPropertySource(featureFlagClient));
+ return new ConfigData(sourceList);
+ }
+
+ private void failedToGeneratePropertySource(Exception e) {
+ logger.error("Fail fast is set and there was an error reading configuration from Azure App "
+ + "Configuration store " + resource.getEndpoint() + ".");
+ delayException();
+ throw new RuntimeException("Failed to generate property sources for " + resource.getEndpoint(), e);
+ }
+
+ /**
+ * Creates a new set of AppConfigurationPropertySources, 1 per Label.
+ *
+ * @param client client for connecting to App Configuration
+ * @return a list of AppConfigurationPropertySources
+ * @throws Exception creating a property source failed
+ */
+ private List createSettings(AppConfigurationReplicaClient client)
+ throws Exception {
+ List sourceList = new ArrayList<>();
+ List selects = resource.getSelects();
+ List profiles = resource.getProfiles().getActive();
+
+ for (AppConfigurationKeyValueSelector selectedKeys : selects) {
+ AppConfigurationPropertySource propertySource = null;
+
+ if (StringUtils.hasText(selectedKeys.getSnapshotName())) {
+ propertySource = new AppConfigurationSnapshotPropertySource(
+ selectedKeys.getSnapshotName() + "/" + resource.getEndpoint(), client, keyVaultClientFactory,
+ selectedKeys.getSnapshotName(), featureFlagClient);
+ } else {
+ propertySource = new AppConfigurationApplicationSettingPropertySource(
+ selectedKeys.getKeyFilter() + resource.getEndpoint() + "/", client, keyVaultClientFactory,
+ selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles));
+ }
+ propertySource.initProperties(resource.getTrimKeyPrefix(), resource.isRefresh());
+ sourceList.add(propertySource);
+ }
+ return sourceList;
+ }
+
+ /**
+ * Creates a new set of AppConfigurationPropertySources, 1 per Label.
+ *
+ * @param client client for connecting to App Configuration
+ * @return a list of AppConfigurationPropertySources
+ * @throws Exception creating a property source failed
+ */
+ private List createFeatureFlags(AppConfigurationReplicaClient client)
+ throws Exception {
+ List featureFlagWatchKeys = new ArrayList<>();
+ List profiles = resource.getProfiles().getActive();
+ for (FeatureFlagKeyValueSelector selectedKeys : resource.getFeatureFlagSelects()) {
+ List storesFeatureFlags = featureFlagClient.loadFeatureFlags(client,
+ selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles), resource.isRefresh());
+ featureFlagWatchKeys.addAll(storesFeatureFlags);
+ }
+
+ return featureFlagWatchKeys;
+ }
+
+ private void delayException() {
+ Instant currentDate = Instant.now();
+ Instant preKillTIme = resource.getAppProperties().getStartDate()
+ .plusSeconds(resource.getAppProperties().getPrekillTime());
+ if (currentDate.isBefore(preKillTIme)) {
+ long diffInMillies = Math.abs(preKillTIme.toEpochMilli() - currentDate.toEpochMilli());
+ try {
+ Thread.sleep(diffInMillies);
+ } catch (InterruptedException e) {
+ logger.error("Failed to wait before fast fail.");
+ }
+ }
+ }
+}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLocationResolver.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLocationResolver.java
new file mode 100644
index 000000000000..77ec7057d854
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLocationResolver.java
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.appconfiguration.config.implementation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.naming.NamingException;
+
+import org.apache.commons.logging.Log;
+import org.springframework.beans.BeanUtils;
+import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
+import org.springframework.boot.context.config.ConfigDataLocation;
+import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
+import org.springframework.boot.context.config.ConfigDataLocationResolver;
+import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
+import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
+import org.springframework.boot.context.config.Profiles;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.properties.bind.BindHandler;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.util.StringUtils;
+
+import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.ConfigStore;
+
+@EnableConfigurationProperties(AppConfigurationProviderProperties.class)
+public class AzureAppConfigDataLocationResolver
+ implements ConfigDataLocationResolver {
+
+ private static final Log LOGGER = new DeferredLog();
+
+ public static final String PREFIX = "azureAppConfiguration";
+
+ private static final AtomicBoolean START_UP = new AtomicBoolean(true);
+
+ @Override
+ public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
+ if (!location.hasPrefix(PREFIX)) {
+ return false;
+ }
+ Boolean hasEndpoint = StringUtils.hasText(context.getBinder()
+ .bind(AppConfigurationProperties.CONFIG_PREFIX + ".stores[0].endpoint", String.class)
+ .orElse(""));
+ Boolean hasConnectionString = StringUtils.hasText(context.getBinder()
+ .bind(AppConfigurationProperties.CONFIG_PREFIX + ".stores[0].connection-string", String.class)
+ .orElse(""));
+ Boolean hasEndpoints = StringUtils.hasText(context.getBinder()
+ .bind(AppConfigurationProperties.CONFIG_PREFIX + ".stores[0].endpoints", String.class)
+ .orElse(""));
+ Boolean hasConnectionStrings = StringUtils.hasText(context.getBinder()
+ .bind(AppConfigurationProperties.CONFIG_PREFIX + ".stores[0].connection-strings", String.class)
+ .orElse(""));
+
+ return (hasEndpoint || hasConnectionString || hasEndpoints || hasConnectionStrings);
+ }
+
+ @Override
+ public List resolve(ConfigDataLocationResolverContext context,
+ ConfigDataLocation location)
+ throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List resolveProfileSpecific(
+ ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, Profiles profiles)
+ throws ConfigDataLocationNotFoundException {
+
+ Holder holder = loadProperties(resolverContext);
+ List locations = new ArrayList<>();
+
+ for (ConfigStore store : holder.properties.getStores()) {
+ locations.add(
+ new AzureAppConfigDataResource(store, profiles, holder.appProperties, START_UP.get(),
+ holder.properties.getRefreshInterval()));
+ }
+ START_UP.set(false);
+ return locations;
+ }
+
+ protected Holder loadProperties(ConfigDataLocationResolverContext context) {
+ Binder binder = context.getBinder();
+ BindHandler bindHandler = getBindHandler(context);
+ AppConfigurationProperties properties;
+ AppConfigurationProviderProperties appProperties;
+ Holder holder = new Holder();
+
+ properties = binder.bind(AppConfigurationProperties.CONFIG_PREFIX,
+ Bindable.of(AppConfigurationProperties.class), bindHandler).get();
+
+ appProperties = binder.bind(AppConfigurationProviderProperties.CONFIG_PREFIX,
+ Bindable.of(AppConfigurationProviderProperties.class), bindHandler)
+ .orElseGet(AppConfigurationProviderProperties::new);
+
+ properties.validateAndInit();
+ ReplicaLookUp replicaLookup = null;
+ try {
+ replicaLookup = new ReplicaLookUp(properties);
+ context.getBootstrapContext().registerIfAbsent(ReplicaLookUp.class, InstanceSupplier.of(replicaLookup));
+ } catch (NamingException e) {
+ LOGGER.info("Failed to find DNS Entry for config store while looking for replicas.");
+ }
+
+ AzureAppConfigurationBootstrapRegistrar.register(context, binder, properties, appProperties, replicaLookup);
+
+ holder.properties = properties;
+ holder.appProperties = appProperties;
+
+ return holder;
+ }
+
+ private BindHandler getBindHandler(ConfigDataLocationResolverContext context) {
+ return context.getBootstrapContext().getOrElse(BindHandler.class, null);
+ }
+
+ private class Holder {
+ AppConfigurationProperties properties;
+
+ AppConfigurationProviderProperties appProperties;
+ }
+
+}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataResource.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataResource.java
new file mode 100644
index 000000000000..13f5f1a87ad8
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataResource.java
@@ -0,0 +1,152 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.appconfiguration.config.implementation;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.context.config.ConfigDataResource;
+import org.springframework.boot.context.config.Profiles;
+
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.ConfigStore;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagKeyValueSelector;
+
+public class AzureAppConfigDataResource extends ConfigDataResource {
+
+ private final boolean configStoreEnabled;
+
+ private final String endpoint;
+
+ private List trimKeyPrefix;
+
+ private final Profiles profiles;
+
+ private List selects = new ArrayList<>();
+
+ private List featureFlagSelects = new ArrayList<>();
+
+ private final AppConfigurationStoreMonitoring monitoring;
+
+ private final AppConfigurationProviderProperties appProperties;
+
+ private final boolean isRefresh;
+
+ private Duration refreshInterval;
+
+ AzureAppConfigDataResource(ConfigStore configStore, Profiles profiles,
+ AppConfigurationProviderProperties appProperties, boolean isRefresh, Duration refreshInterval) {
+ this.configStoreEnabled = configStore.isEnabled();
+ this.endpoint = configStore.getEndpoint();
+ this.selects = configStore.getSelects();
+ this.featureFlagSelects = configStore.getFeatureFlags().getSelects();
+ this.trimKeyPrefix = configStore.getTrimKeyPrefix();
+ this.monitoring = configStore.getMonitoring();
+ this.profiles = profiles;
+ this.appProperties = appProperties;
+ this.isRefresh = isRefresh;
+ this.refreshInterval = refreshInterval;
+ }
+
+ /**
+ * @return the selects
+ */
+ public List getSelects() {
+ return selects;
+ }
+
+ /**
+ * @param selects the selects to set
+ */
+ public void setSelects(List selects) {
+ this.selects = selects;
+ }
+
+ /**
+ * @return the selects for feature flags
+ */
+ public List getFeatureFlagSelects() {
+ return featureFlagSelects;
+ }
+
+ /**
+ * @param featureFlagSelects the selects to set
+ */
+ public void setFeatureFlagSelects(List featureFlagSelects) {
+ this.featureFlagSelects = featureFlagSelects;
+ }
+
+ /**
+ * @return the configStoreEnabled
+ */
+ public boolean isConfigStoreEnabled() {
+ return configStoreEnabled;
+ }
+
+ /**
+ * @return the endpoint
+ */
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ /**
+ * @return the monitoring
+ */
+ public AppConfigurationStoreMonitoring getMonitoring() {
+ return monitoring;
+ }
+
+ /**
+ * @return the trimKeyPrefix
+ */
+ public List getTrimKeyPrefix() {
+ return trimKeyPrefix;
+ }
+
+ /**
+ * @param trimKeyPrefix the trimKeyPrefix to set
+ */
+ public void setTrimKeyPrefix(List trimKeyPrefix) {
+ this.trimKeyPrefix = trimKeyPrefix;
+ }
+
+ /**
+ * @return the profiles
+ */
+ public Profiles getProfiles() {
+ return profiles;
+ }
+
+ /**
+ * @return the isRefresh
+ */
+ public boolean isRefresh() {
+ return isRefresh;
+ }
+
+ /**
+ * @return the appProperties
+ */
+ public AppConfigurationProviderProperties getAppProperties() {
+ return appProperties;
+ }
+
+ /**
+ * @return the refreshInterval
+ */
+ public Duration getRefreshInterval() {
+ return refreshInterval;
+ }
+
+ /**
+ * @param refreshInterval the refreshInterval to set
+ */
+ public void setRefreshInterval(Duration refreshInterval) {
+ this.refreshInterval = refreshInterval;
+ }
+
+}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManager.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManager.java
index 16c6d2d05fdc..17124ea72904 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManager.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManager.java
@@ -7,7 +7,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,7 +20,7 @@
/**
* Holds a set of connections to an app configuration store with zero to many geo-replications.
*/
-public class ConnectionManager {
+class ConnectionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class);
@@ -143,13 +142,6 @@ List getAvailableClients(Boolean useCurrent) {
return availableClients;
}
- List getAllEndpoints() {
- List endpoints = clients.stream().map(AppConfigurationReplicaClient::getEndpoint)
- .collect(Collectors.toList());
- endpoints.addAll(replicaLookUp.getAutoFailoverEndpoints(configStore.getEndpoint()));
- return endpoints;
- }
-
/**
* Call when the current client failed
* @param endpoint replica endpoint
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
index 956454d616ca..474f0fda49f0 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
@@ -14,6 +14,8 @@
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.TELEMETRY;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -21,7 +23,6 @@
import java.util.List;
import java.util.Map;
-import org.bouncycastle.jcajce.provider.digest.SHA256;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -43,9 +44,9 @@
* take priority.
*/
@Component
-public class FeatureFlagClient {
+class FeatureFlagClient {
- protected final Map properties = new LinkedHashMap<>();
+ private final Map properties = new LinkedHashMap<>();
private static final ObjectMapper CASE_INSENSITIVE_MAPPER = JsonMapper.builder()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();
@@ -62,7 +63,7 @@ public class FeatureFlagClient {
*
*
*/
- public List loadFeatureFlags(AppConfigurationReplicaClient replicaClient, String customKeyFilter,
+ List loadFeatureFlags(AppConfigurationReplicaClient replicaClient, String customKeyFilter,
String[] labelFilter, boolean isRefresh) {
List loadedFeatureFlags = new ArrayList<>();
@@ -84,7 +85,7 @@ public List loadFeatureFlags(AppConfigurationReplicaClient replica
return loadedFeatureFlags;
}
- public List proccessFeatureFlags(FeatureFlags features, String endpoint) {
+ List proccessFeatureFlags(FeatureFlags features, String endpoint) {
List loadedFeatureFlags = new ArrayList<>();
loadedFeatureFlags.add(features);
@@ -151,11 +152,15 @@ protected static Feature createFeature(FeatureFlagConfigurationSetting item, Str
*/
private static String calculateFeatureFlagId(String key, String label) {
final String data = String.format("%s\n%s", key, label.isEmpty() ? null : label);
- final SHA256.Digest digest = new SHA256.Digest();
- final String beforeTrim = Base64URL.encode(digest.digest(data.getBytes(StandardCharsets.UTF_8)))
- .toString().replace('+', '-').replace('/', '_');
- final int index = beforeTrim.indexOf('=');
- return beforeTrim.substring(0, index > -1 ? index : beforeTrim.length());
+ try {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ final String beforeTrim = Base64URL.encode(sha256.digest(data.getBytes(StandardCharsets.UTF_8)))
+ .toString().replace('+', '-').replace('/', '_');
+ final int index = beforeTrim.indexOf('=');
+ return beforeTrim.substring(0, index > -1 ? index : beforeTrim.length());
+ } catch (NoSuchAlgorithmException e) {
+ }
+ return "";
}
/**
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/NormalizeNull.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/NormalizeNull.java
index 8463930ba1c9..3ae7ac5f267b 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/NormalizeNull.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/NormalizeNull.java
@@ -9,7 +9,7 @@
* configurations with any label.
*
*/
-public class NormalizeNull {
+class NormalizeNull {
private static final String EMPTY_LABEL = "\0";
@@ -19,7 +19,7 @@ public class NormalizeNull {
* @param setting ConfigurationSetting
* @return ConfigurationSetting with label corrected from null to \0
*/
- public static ConfigurationSetting normalizeNullLabel(ConfigurationSetting setting) {
+ static ConfigurationSetting normalizeNullLabel(ConfigurationSetting setting) {
return setting.getLabel() == null ? setting.setLabel(EMPTY_LABEL) : setting;
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
index 9de8f3d08228..817ee27d1c6c 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
@@ -132,14 +132,9 @@ Map getLoadState() {
/**
* @param originEndpoint the configuration store connected to.
* @param loaded true if the configuration store was loaded.
- * @param failFast application started after it failed to load from a store.
*/
- void setLoadState(String originEndpoint, Boolean loaded, Boolean failFast) {
- if (loaded || !failFast) {
- loadState.put(originEndpoint, true);
- } else {
- loadState.put(originEndpoint, false);
- }
+ void setLoadState(String originEndpoint, Boolean loaded) {
+ loadState.put(originEndpoint, loaded);
}
/**
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/autofailover/SRVRecord.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/autofailover/SRVRecord.java
index 9646b83527d9..7b7e9c1225f2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/autofailover/SRVRecord.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/autofailover/SRVRecord.java
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation.autofailover;
-public class SRVRecord {
+class SRVRecord {
private final int priority;
@@ -14,7 +14,7 @@ public class SRVRecord {
private static final String PROTOCAL = "https://";
- public SRVRecord(String[] record) {
+ SRVRecord(String[] record) {
this.priority = Integer.valueOf(record[0]);
this.weight = Integer.valueOf(record[1]);
this.port = Integer.valueOf(record[2]);
@@ -41,7 +41,7 @@ public String getEndpoint() {
return PROTOCAL + target;
}
- public int compareTo(SRVRecord record) {
+ int compareTo(SRVRecord record) {
if (priority > record.getPriority()) {
return 1;
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfiguration.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfiguration.java
deleted file mode 100644
index 2573d3abf766..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfiguration.java
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.appconfiguration.config.implementation.config;
-
-import javax.naming.NamingException;
-
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.context.properties.bind.Binder;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.PropertySource;
-import org.springframework.core.env.Environment;
-import org.springframework.scheduling.annotation.EnableAsync;
-import org.springframework.util.StringUtils;
-
-import com.azure.data.appconfiguration.ConfigurationClientBuilder;
-import com.azure.spring.cloud.appconfiguration.config.ConfigurationClientCustomizer;
-import com.azure.spring.cloud.appconfiguration.config.KeyVaultSecretProvider;
-import com.azure.spring.cloud.appconfiguration.config.SecretClientCustomizer;
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationKeyVaultClientFactory;
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationPropertySourceLocator;
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationReplicaClientFactory;
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationReplicaClientsBuilder;
-import com.azure.spring.cloud.appconfiguration.config.implementation.FeatureFlagClient;
-import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.appconfiguration.AzureAppConfigurationProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.keyvault.secrets.properties.AzureKeyVaultSecretProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.properties.core.AbstractAzureHttpConfigurationProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.properties.core.authentication.TokenCredentialConfigurationProperties;
-import com.azure.spring.cloud.autoconfigure.implementation.properties.utils.AzureGlobalPropertiesUtils;
-import com.azure.spring.cloud.core.customizer.AzureServiceClientBuilderCustomizer;
-import com.azure.spring.cloud.core.implementation.util.AzurePropertiesUtils;
-import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier;
-import com.azure.spring.cloud.service.implementation.appconfiguration.ConfigurationClientBuilderFactory;
-import com.azure.spring.cloud.service.implementation.keyvault.secrets.SecretClientBuilderFactory;
-
-/**
- * Setup ConnectionPool, AppConfigurationPropertySourceLocator, and ClientStore when
- * spring.cloud.azure.appconfiguration.enabled is enabled.
- */
-@Configuration
-@PropertySource("classpath:appConfiguration.properties")
-@EnableConfigurationProperties({ AppConfigurationProperties.class, AppConfigurationProviderProperties.class })
-@ConditionalOnClass(AppConfigurationPropertySourceLocator.class)
-@ConditionalOnProperty(prefix = AppConfigurationProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
-@EnableAsync
-public class AppConfigurationBootstrapConfiguration {
-
- @Autowired
- private transient ApplicationContext context;
-
- @Bean
- AppConfigurationPropertySourceLocator sourceLocator(AppConfigurationProperties properties,
- AppConfigurationProviderProperties appProperties, AppConfigurationReplicaClientFactory clientFactory,
- AppConfigurationKeyVaultClientFactory keyVaultClientFactory, ReplicaLookUp replicaLookUp,
- FeatureFlagClient featureFlagLoader)
- throws IllegalArgumentException {
-
- return new AppConfigurationPropertySourceLocator(appProperties, clientFactory, keyVaultClientFactory,
- properties.getRefreshInterval(), properties.getStores(), replicaLookUp, featureFlagLoader);
- }
-
- @Bean
- AppConfigurationKeyVaultClientFactory appConfigurationKeyVaultClientFactory(Environment environment,
- AppConfigurationProviderProperties appProperties)
- throws IllegalArgumentException {
- AzureGlobalProperties globalSource = Binder.get(environment).bindOrCreate(AzureGlobalProperties.PREFIX,
- AzureGlobalProperties.class);
- AzureGlobalProperties serviceSource = Binder.get(environment).bindOrCreate(AzureKeyVaultSecretProperties.PREFIX,
- AzureGlobalProperties.class);
-
- AzureKeyVaultSecretProperties globalProperties = AzureGlobalPropertiesUtils.loadProperties(
- globalSource,
- new AzureKeyVaultSecretProperties());
- AzureKeyVaultSecretProperties clientProperties = AzureGlobalPropertiesUtils.loadProperties(serviceSource,
- new AzureKeyVaultSecretProperties());
-
- AzurePropertiesUtils.copyAzureCommonPropertiesIgnoreNull(globalProperties, clientProperties);
-
- SecretClientCustomizer keyVaultClientProvider = context.getBeanProvider(SecretClientCustomizer.class)
- .getIfAvailable();
- KeyVaultSecretProvider keyVaultSecretProvider = context.getBeanProvider(KeyVaultSecretProvider.class)
- .getIfAvailable();
-
- SecretClientBuilderFactory secretClientBuilderFactory = new SecretClientBuilderFactory(clientProperties);
-
- boolean credentialConfigured = isCredentialConfigured(clientProperties);
-
- return new AppConfigurationKeyVaultClientFactory(keyVaultClientProvider, keyVaultSecretProvider,
- secretClientBuilderFactory, credentialConfigured, appProperties.getMaxRetryTime());
- }
-
- /**
- * Factory for working with App Configuration Clients
- *
- * @param clientBuilder Builder for configuration clients
- * @param properties Client configurations for setting up connections to each config store.
- * @return AppConfigurationReplicaClientFactory
- */
- @Bean
- @ConditionalOnMissingBean
- AppConfigurationReplicaClientFactory buildClientFactory(AppConfigurationReplicaClientsBuilder clientBuilder,
- AppConfigurationProperties properties, ReplicaLookUp replicaLookUp) {
- return new AppConfigurationReplicaClientFactory(clientBuilder, properties.getStores(), replicaLookUp);
- }
-
- /**
- * Loader for all feature flags. Enables de-duplicating of feature flags when multiple feature flags with the same
- * name are loaded.
- * @return {@link FeatureFlagClient}
- */
- @Bean
- @ConditionalOnMissingBean
- FeatureFlagClient featureFlagLoader() {
- return new FeatureFlagClient();
- }
-
- /**
- * Builder for clients connecting to App Configuration.
- *
- * @param clientProperties AzureAppConfigurationProperties Spring Cloud Azure global properties.
- * @param appProperties Library configurations for setting up connections to each config store.
- * @param keyVaultClientFactory used for tracing info for if key vault has been configured
- * @param customizers Client Customizers for connecting to Azure App Configuration
- * @return ClientStore
- */
- @Bean
- @ConditionalOnMissingBean
- AppConfigurationReplicaClientsBuilder replicaClientBuilder(Environment environment,
- AppConfigurationProviderProperties appProperties, AppConfigurationKeyVaultClientFactory keyVaultClientFactory,
- ObjectProvider> customizers) {
- AzureGlobalProperties globalSource = Binder.get(environment).bindOrCreate(AzureGlobalProperties.PREFIX,
- AzureGlobalProperties.class);
- AzureGlobalProperties serviceSource = Binder.get(environment).bindOrCreate(
- AzureAppConfigurationProperties.PREFIX,
- AzureGlobalProperties.class);
-
- AzureGlobalProperties globalProperties = AzureGlobalPropertiesUtils.loadProperties(globalSource,
- new AzureGlobalProperties());
- AzureAppConfigurationProperties clientProperties = AzureGlobalPropertiesUtils.loadProperties(serviceSource,
- new AzureAppConfigurationProperties());
-
- AzurePropertiesUtils.copyAzureCommonPropertiesIgnoreNull(globalProperties, clientProperties);
-
- ConfigurationClientBuilderFactory clientFactory = new ConfigurationClientBuilderFactory(clientProperties);
-
- clientFactory.setSpringIdentifier(AzureSpringIdentifier.AZURE_SPRING_APP_CONFIG);
- customizers.orderedStream().forEach(clientFactory::addBuilderCustomizer);
-
- boolean credentialConfigured = isCredentialConfigured(clientProperties);
-
- AppConfigurationReplicaClientsBuilder clientBuilder = new AppConfigurationReplicaClientsBuilder(
- appProperties.getMaxRetries(), clientFactory, credentialConfigured);
-
- clientBuilder
- .setClientProvider(context.getBeanProvider(ConfigurationClientCustomizer.class)
- .getIfAvailable());
-
- clientBuilder.setIsKeyVaultConfigured(keyVaultClientFactory.isConfigured());
-
- return clientBuilder;
- }
-
- @Bean
- ReplicaLookUp replicaLookUp(AppConfigurationProperties properties) throws NamingException {
- return new ReplicaLookUp(properties);
- }
-
- private boolean isCredentialConfigured(AbstractAzureHttpConfigurationProperties properties) {
- if (properties.getCredential() != null) {
- TokenCredentialConfigurationProperties tokenProps = properties.getCredential();
- if (StringUtils.hasText(tokenProps.getClientCertificatePassword())) {
- return true;
- } else if (StringUtils.hasText(tokenProps.getClientCertificatePath())) {
- return true;
- } else if (StringUtils.hasText(tokenProps.getClientId())) {
- return true;
- } else if (StringUtils.hasText(tokenProps.getClientSecret())) {
- return true;
- } else if (StringUtils.hasText(tokenProps.getUsername())) {
- return true;
- } else if (StringUtils.hasText(tokenProps.getPassword())) {
- return true;
- }
- }
-
- return false;
- }
-
-}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/FeatureFlags.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/FeatureFlags.java
index dc725a309000..2a1380a3d907 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/FeatureFlags.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/FeatureFlags.java
@@ -6,7 +6,6 @@
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.ConfigStore;
public class FeatureFlags {
@@ -14,27 +13,11 @@ public class FeatureFlags {
private List featureFlags;
- private ConfigStore configStore;
-
public FeatureFlags(SettingSelector settingSelector, List featureFlags) {
this.settingSelector = settingSelector;
this.featureFlags = featureFlags;
}
- /**
- * @return the configStore
- */
- public ConfigStore getConfigStore() {
- return configStore;
- }
-
- /**
- * @param configStore the configStore to set
- */
- public void setConfigStore(ConfigStore configStore) {
- this.configStore = configStore;
- }
-
/**
* @return the settingSelector
*/
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/Conditions.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/Conditions.java
index d882c0399021..e010edfe7e8f 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/Conditions.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/Conditions.java
@@ -20,7 +20,7 @@ public class Conditions {
@JsonProperty("requirement_type")
private String requirementType = DEFAULT_REQUIREMENT_TYPE;
- public Conditions(List featureFilters, String requirementType) {
+ Conditions(List featureFilters, String requirementType) {
clientFilters = new ArrayList<>();
clientFilters.addAll(featureFilters);
this.requirementType = requirementType;
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureFilterEvaluationContext.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureFilterEvaluationContext.java
deleted file mode 100644
index 760284fc57bd..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureFilterEvaluationContext.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/**
- * Context passed into Feature Filters used for evaluation.
- */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public final class FeatureFilterEvaluationContext {
-
- /**
- * Creates an instance of {@link FeatureFilterEvaluationContext}
- */
- public FeatureFilterEvaluationContext() {
- }
-
- private String name;
-
- private Map parameters;
-
- /**
- * Return the name
- * @return the name
- */
- public String getName() {
- return name;
- }
-
- /**
- * Set the name
- * @param name the name to set
- */
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * Return the parameters
- * @return the parameters
- */
- public Map getParameters() {
- Map params = new HashMap();
- if (parameters != null) {
- params.putAll(parameters);
- }
- return params;
- }
-
- /**
- * Set the parameters
- * @param parameters the parameters to set
- */
- public void setParameters(Map parameters) {
- this.parameters = parameters;
- }
-
-}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureSet.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureSet.java
deleted file mode 100644
index 5259f28a3b11..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/feature/entity/FeatureSet.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity;
-
-import java.util.HashMap;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Set of Feature Flag Key pairs.
- */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public final class FeatureSet {
-
- @JsonProperty("FeatureManagement")
- private HashMap featureManagement;
-
- /**
- * Returns Map of Feature Flags.
- *
- * @return the featureFlags
- */
- public HashMap getFeatureManagement() {
- return featureManagement;
- }
-
- /**
- * Adds a new Feature Flag.
- *
- * @param key Name of the Feature Flag.
- * @param feature true/false, for on/off feature Flag. {@code Feature} if Feature Filter.
- */
- public void addFeature(String key, Object feature) {
- if (featureManagement == null) {
- featureManagement = new HashMap<>();
- }
- if (feature != null) {
- featureManagement.put(key, feature);
- }
- }
-}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
index 0f5db1c2ee38..bd72467aa3f9 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
@@ -27,10 +27,10 @@ public final class BaseAppConfigurationPolicy implements HttpPipelinePolicy {
/**
* Format of User Agent
*/
- public static final String USER_AGENT = String.format("%s/%s", StringUtils.replace(PACKAGE_NAME, " ", ""),
+ private static final String USER_AGENT = String.format("%s/%s", StringUtils.replace(PACKAGE_NAME, " ", ""),
BaseAppConfigurationPolicy.class.getPackage().getImplementationVersion());
- final TracingInfo tracingInfo;
+ private final TracingInfo tracingInfo;
/**
* App Configuration Http Pipeline Policy
@@ -40,6 +40,7 @@ public BaseAppConfigurationPolicy(TracingInfo tracingInfo) {
this.tracingInfo = tracingInfo;
}
+
@Override
public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
Boolean watchRequests = (Boolean) context.getData("refresh").orElse(false);
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/FeatureFlagTracing.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/FeatureFlagTracing.java
index 8a325a4614ce..24b62193cc55 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/FeatureFlagTracing.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/FeatureFlagTracing.java
@@ -5,7 +5,7 @@
import java.util.Arrays;
import java.util.List;
-public class FeatureFlagTracing {
+class FeatureFlagTracing {
private static final String CUSTOM_FILTER = "CSTM";
@@ -34,18 +34,18 @@ public class FeatureFlagTracing {
private Boolean usesTargetingFilter = false;
- public boolean usesAnyFilter() {
+ boolean usesAnyFilter() {
return usesCustomFilter || usesPercentageFilter || usesTimeWindowFilter || usesTargetingFilter;
}
- public void resetFeatureFilterTelemetry() {
+ void resetFeatureFilterTelemetry() {
usesCustomFilter = false;
usesPercentageFilter = false;
usesTimeWindowFilter = false;
usesTargetingFilter = false;
}
- public void updateFeatureFilterTelemetry(String filterName) {
+ void updateFeatureFilterTelemetry(String filterName) {
if (PERCENTAGE_FILTER_NAMES.stream().anyMatch(name -> name.equalsIgnoreCase(filterName))) {
usesPercentageFilter = true;
} else if (TIME_WINDOW_FILTER_NAMES.stream().anyMatch(name -> name.equalsIgnoreCase(filterName))) {
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
index e9608eb72db2..5f57b8e9ab78 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation.http.policy;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.DEV_ENV_TRACING;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.KEY_VAULT_CONFIGURED_TRACING;
import org.springframework.util.StringUtils;
@@ -14,8 +13,6 @@
public class TracingInfo {
- private boolean isDev = false;
-
private boolean isKeyVaultConfigured = false;
private int replicaCount;
@@ -24,17 +21,15 @@ public class TracingInfo {
private final Configuration configuration;
- public TracingInfo(boolean isDev, boolean isKeyVaultConfigured, int replicaCount, Configuration configuration) {
- this.isDev = isDev;
+ public TracingInfo(boolean isKeyVaultConfigured, int replicaCount, Configuration configuration) {
this.isKeyVaultConfigured = isKeyVaultConfigured;
this.replicaCount = replicaCount;
this.featureFlagTracing = new FeatureFlagTracing();
this.configuration = configuration;
}
- public String getValue(boolean watchRequests) {
- String track = configuration
- .get(RequestTracingConstants.REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE.toString());
+ String getValue(boolean watchRequests) {
+ String track = configuration.get(RequestTracingConstants.REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE.toString());
if (track != null && Boolean.valueOf(track)) {
return "";
}
@@ -52,10 +47,6 @@ public String getValue(boolean watchRequests) {
if (!hostType.isEmpty()) {
sb.append(",").append(RequestTracingConstants.HOST_TYPE_KEY).append("=").append(hostType);
}
-
- if (isDev) {
- sb.append(",Env=").append(DEV_ENV_TRACING);
- }
if (isKeyVaultConfigured) {
sb.append(",").append(KEY_VAULT_CONFIGURED_TRACING);
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationKeyValueSelector.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationKeyValueSelector.java
index d6d4baed1efd..3b210621899d 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationKeyValueSelector.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationKeyValueSelector.java
@@ -30,7 +30,7 @@ public final class AppConfigurationKeyValueSelector {
/**
* Separator for multiple labels
*/
- public static final String LABEL_SEPARATOR = ",";
+ private static final String LABEL_SEPARATOR = ",";
@NotNull
/**
@@ -129,7 +129,7 @@ public void setSnapshotName(String snapshotName) {
* Validates key-filter and label-filter are valid.
*/
@PostConstruct
- public void validateAndInit() {
+ void validateAndInit() {
Assert.isTrue(!keyFilter.contains("*"), "KeyFilter must not contain asterisk(*)");
if (labelFilter != null) {
Assert.isTrue(!labelFilter.contains("*"), "LabelFilter must not contain asterisk(*)");
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreMonitoring.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreMonitoring.java
index 22db1862d354..f90a3a573f12 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreMonitoring.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreMonitoring.java
@@ -118,7 +118,7 @@ public void setPushNotification(PushNotification pushNotification) {
* Validates refreshIntervals are at least 1 second, and if enabled triggers are valid.
*/
@PostConstruct
- public void validateAndInit() {
+ void validateAndInit() {
if (enabled) {
Assert.notEmpty(triggers, "Triggers need to be set if refresh is enabled.");
for (AppConfigurationStoreTrigger trigger : triggers) {
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreTrigger.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreTrigger.java
index ec69376e1bd7..97499d8b3f23 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreTrigger.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationStoreTrigger.java
@@ -58,7 +58,7 @@ public void setLabel(String label) {
* Validates key isn't null
*/
@PostConstruct
- public void validateAndInit() {
+ void validateAndInit() {
Assert.notNull(key, "All Triggers need a key value set.");
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/ConfigStore.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/ConfigStore.java
index 7444d739770f..0f344b1637a2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/ConfigStore.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/ConfigStore.java
@@ -53,15 +53,6 @@ public final class ConfigStore {
*/
private List selects = new ArrayList<>();
- /**
- * If true, the application will fail to start if the Config Store cannot be
- * reached. If false, the application will start without the Config Store.
- */
- private boolean failFast = true;
-
- /**
- * Options for retrieving Feature Flags from the Azure Config Service.
- */
private FeatureFlagStore featureFlags = new FeatureFlagStore();
/**
@@ -147,20 +138,6 @@ public void setConnectionStrings(List connectionStrings) {
this.connectionStrings = connectionStrings;
}
- /**
- * @return the failFast
- */
- public boolean isFailFast() {
- return failFast;
- }
-
- /**
- * @param failFast the failFast to set
- */
- public void setFailFast(boolean failFast) {
- this.failFast = failFast;
- }
-
/**
* @return the enabled
*/
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagKeyValueSelector.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagKeyValueSelector.java
index b3280178e200..4d1de6b90b11 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagKeyValueSelector.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagKeyValueSelector.java
@@ -105,7 +105,7 @@ public FeatureFlagKeyValueSelector setLabelFilter(String labelFilter) {
* Validates key-filter and label-filter are valid.
*/
@PostConstruct
- public void validateAndInit() {
+ void validateAndInit() {
if (labelFilter != null) {
Assert.isTrue(!labelFilter.contains("*"), "LabelFilter must not contain asterisk(*)");
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagStore.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagStore.java
index c7b5e89646f7..18ee7c2f28b3 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagStore.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/FeatureFlagStore.java
@@ -48,7 +48,7 @@ public void setSelects(List selects) {
}
@PostConstruct
- public void validateAndInit() {
+ void validateAndInit() {
if (enabled && selects.size() == 0) {
selects.add(new FeatureFlagKeyValueSelector());
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring.factories b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring.factories
index 9a76c2325a3c..fb0f411d86c2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring.factories
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring.factories
@@ -1,3 +1,5 @@
-org.springframework.cloud.bootstrap.BootstrapConfiguration=\
-com.azure.spring.cloud.appconfiguration.config.implementation.config.AppConfigurationBootstrapConfiguration
+# ConfigData Location Resolvers
+ org.springframework.boot.context.config.ConfigDataLocationResolver= com.azure.spring.cloud.appconfiguration.config.implementation.AzureAppConfigDataLocationResolver
+ # ConfigData Loaders
+ org.springframework.boot.context.config.ConfigDataLoader= com.azure.spring.cloud.appconfiguration.config.implementation.AzureAppConfigDataLoader
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 1b36b907ed1b..0472d30fc7d2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,2 +1 @@
-com.azure.spring.cloud.appconfiguration.config.AppConfigurationAutoConfiguration
-com.azure.spring.cloud.appconfiguration.config.implementation.config.AppConfigurationBootstrapConfiguration
\ No newline at end of file
+com.azure.spring.cloud.appconfiguration.config.AppConfigurationWatchAutoConfiguration
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.properties b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.properties
deleted file mode 100644
index 9b90ce63ee85..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-spring.cloud.appconfiguration.version=1.0
-spring.cloud.appconfiguration.maxRetries=2
-spring.cloud.appconfiguration.maxRetryTime=60
-spring.cloud.appconfiguration.preKillTime=5
-spring.cloud.appconfiguration.defaultMinBackoff=30
-spring.cloud.appconfiguration.defaultmaxBackoff=600
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
index bd48c729a109..7ef8af30b518 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
@@ -128,10 +128,10 @@ public void cleanup() throws Exception {
@Test
public void testPropCanBeInitAndQueried() throws IOException {
when(configurationListMock.iterator()).thenReturn(testItems.iterator()).thenReturn(testItems.iterator());
- when(clientMock.listSettingSnapshot(Mockito.any())).thenReturn(configurationListMock)
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(TRIM);
+ propertySource.initProperties(TRIM, false);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames = testItems.stream().map(t -> {
@@ -155,10 +155,10 @@ public void testPropertyNameSlashConvertedToDots() throws IOException {
List settings = new ArrayList<>();
settings.add(slashedProp);
when(configurationListMock.iterator()).thenReturn(settings.iterator()).thenReturn(Collections.emptyIterator());
- when(clientMock.listSettingSnapshot(Mockito.any())).thenReturn(configurationListMock)
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(TRIM);
+ propertySource.initProperties(TRIM, false);
String expectedKeyName = TEST_SLASH_KEY.replace('/', '.');
String[] actualKeyNames = propertySource.getPropertyNames();
@@ -174,9 +174,9 @@ public void initNullValidContentTypeTest() throws IOException {
List items = new ArrayList<>();
items.add(ITEM_NULL);
when(configurationListMock.iterator()).thenReturn(items.iterator()).thenReturn(Collections.emptyIterator());
- when(clientMock.listSettingSnapshot(Mockito.any())).thenReturn(configurationListMock);
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock);
- propertySource.initProperties(TRIM);
+ propertySource.initProperties(TRIM, false);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames =
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
index 2833cc92970c..0bc44e5c546c 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.KEY_VAULT_CONTENT_TYPE;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.KEY_VAULT_CONTENT_TYPE;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_CONN_STRING;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_KEY_VAULT_1;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_LABEL_VAULT_1;
@@ -146,8 +146,7 @@ public void invalidKeyVaultReferenceParseErrorTest() {
.thenReturn(clientManagerMock);
when(clientManagerMock.getSecret(Mockito.any())).thenThrow(new RuntimeException("Parse Failed"));
- RuntimeException exception = assertThrows(RuntimeException.class,
- () -> propertySource.initProperties(null, false));
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> propertySource.initProperties(null, false));
assertEquals("Parse Failed", exception.getMessage());
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceLocatorTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceLocatorTest.java
deleted file mode 100644
index a68ac60e28e6..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceLocatorTest.java
+++ /dev/null
@@ -1,568 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.appconfiguration.config.implementation;
-
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.EMPTY_LABEL;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_CONN_STRING;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_CONN_STRING_2;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_STORE_NAME;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_STORE_NAME_2;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-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.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-import org.mockito.Mock;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-import org.springframework.core.env.CompositePropertySource;
-import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MutablePropertySources;
-import org.springframework.core.env.PropertySource;
-
-import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
-import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
-import com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity.Feature;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreTrigger;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.ConfigStore;
-import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagStore;
-
-@TestMethodOrder(MethodOrderer.MethodName.class)
-public class AppConfigurationPropertySourceLocatorTest {
-
- private static final String PROFILE_NAME_1 = "dev";
-
- private static final String PROFILE_NAME_2 = "prod";
-
- private static final String KEY_FILTER = "/foo/";
-
- @Mock
- private ConfigurableEnvironment emptyEnvironment;
-
- @Mock
- private ConfigurableEnvironment devEnvironment;
-
- @Mock
- private ConfigurableEnvironment multiEnvironment;
-
- @Mock
- private AppConfigurationReplicaClientFactory clientFactoryMock;
-
- @Mock
- private AppConfigurationKeyVaultClientFactory keyVaultClientFactory;
-
- @Mock
- private AppConfigurationReplicaClient replicaClientMock;
-
- @Mock
- private FeatureFlagStore featureFlagStoreMock;
-
- @Mock
- private ConfigStore configStoreMockError;
-
- @Mock
- private AppConfigurationProviderProperties appPropertiesMock;
-
- @Mock
- private ReplicaLookUp replicaLookUpMock;
-
- @Mock
- private FeatureFlagClient featureFlagClientMock;
-
- @Mock
- private ConfigStore configStoreMock;
-
- private AppConfigurationPropertySourceLocator locator;
-
- private AppConfigurationProperties properties;
-
- private AppConfigurationProviderProperties appProperties;
-
- private List stores;
-
- private AppConfigurationStoreMonitoring monitoring;
-
- private MutablePropertySources sources = new MutablePropertySources();
-
- private MockitoSession session;
-
- @BeforeEach
- public void setup() {
- session = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();
- MockitoAnnotations.openMocks(this);
-
- sources.addFirst(new PropertySource("refreshArgs") {
-
- @Override
- public Object getProperty(String name) {
- return null;
- }
- });
-
- properties = new AppConfigurationProperties();
- properties.setEnabled(true);
- properties.setRefreshInterval(null);
-
- TestUtils.addStore(properties, TEST_STORE_NAME, TEST_CONN_STRING, KEY_FILTER);
-
- monitoring = new AppConfigurationStoreMonitoring();
- monitoring.setEnabled(false);
- AppConfigurationStoreTrigger trigger = new AppConfigurationStoreTrigger();
- trigger.setKey("test");
-
- monitoring.setTriggers(List.of(trigger));
-
- appProperties = new AppConfigurationProviderProperties();
- appProperties.setVersion("1.0");
- appProperties.setMaxRetries(12);
- appProperties.setMaxRetryTime(0);
- appProperties.setDefaultMaxBackoff((long) 600);
- appProperties.setDefaultMinBackoff((long) 30);
-
- properties.getStores().get(0).setFeatureFlags(featureFlagStoreMock);
- properties.getStores().get(0).setMonitoring(monitoring);
- stores = properties.getStores();
- }
-
- @AfterEach
- public void cleanup() throws Exception {
- MockitoAnnotations.openMocks(this).close();
- session.finishMocking();
- AppConfigurationPropertySourceLocator.STARTUP.set(true);
- }
-
- @Test
- public void compositeSourceIsCreated() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock,
- keyVaultClientFactory, null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
-
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void compositeSourceIsCreatedWithMonitoring() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- String watchKey = "wk1";
- AppConfigurationStoreMonitoring monitoring = new AppConfigurationStoreMonitoring();
- monitoring.setEnabled(true);
-
- AppConfigurationStoreTrigger trigger = new AppConfigurationStoreTrigger();
- trigger.setKey(watchKey);
- trigger.setLabel(EMPTY_LABEL);
- monitoring.setTriggers(List.of(trigger));
-
- properties.getStores().get(0).setMonitoring(monitoring);
-
- when(replicaClientMock.getWatchKey(Mockito.eq(watchKey), Mockito.anyString(), Mockito.anyBoolean()))
- .thenReturn(TestUtils.createItem("", watchKey, "0", EMPTY_LABEL, ""));
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
-
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- verify(replicaClientMock, times(1)).getWatchKey(Mockito.eq(watchKey), Mockito.anyString(),
- Mockito.anyBoolean());
- }
- }
-
- @Test
- public void compositeSourceIsCreatedWithMonitoringWatchKeyDoesNotExist() {
- // The listed Watch Key doesn't have a value in app config. When one is added will cause a refresh.
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- String watchKey = "wk1";
- AppConfigurationStoreMonitoring monitoring = new AppConfigurationStoreMonitoring();
- monitoring.setEnabled(true);
-
- AppConfigurationStoreTrigger trigger = new AppConfigurationStoreTrigger();
- trigger.setKey(watchKey);
- trigger.setLabel(EMPTY_LABEL);
- monitoring.setTriggers(List.of(trigger));
-
- properties.getStores().get(0).setMonitoring(monitoring);
-
- when(replicaClientMock.getWatchKey(Mockito.eq(watchKey), Mockito.anyString(), Mockito.anyBoolean()))
- .thenReturn(null);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
-
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- verify(replicaClientMock, times(1)).getWatchKey(Mockito.eq(watchKey), Mockito.anyString(),
- Mockito.anyBoolean());
- }
- }
-
- @Test
- public void devSourceIsCreated() {
- when(devEnvironment.getActiveProfiles()).thenReturn(new String[] { PROFILE_NAME_1 });
- when(devEnvironment.getPropertySources()).thenReturn(sources);
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of());
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(devEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/dev"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void multiSourceIsCreated() {
- when(multiEnvironment.getActiveProfiles()).thenReturn(new String[] { PROFILE_NAME_1, PROFILE_NAME_2 });
- when(multiEnvironment.getPropertySources()).thenReturn(sources);
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of());
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(multiEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/prod,dev"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void storeCreatedWithFeatureFlags() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of("fake_features", new Feature()));
-
- FeatureFlagStore featureFlagStore = new FeatureFlagStore();
- featureFlagStore.setEnabled(true);
- featureFlagStore.validateAndInit();
-
- FeatureFlagConfigurationSetting featureFlag = new FeatureFlagConfigurationSetting("Alpha", false);
- featureFlag.setValue("");
-
- properties.getStores().get(0).setFeatureFlags(featureFlagStore);
-
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of(featureFlag));
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0",
- "feature_management"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void storeCreatedWithFeatureFlagsWithMonitoring() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of("fake_features", new Feature()));
-
- AppConfigurationStoreMonitoring monitoring = new AppConfigurationStoreMonitoring();
- monitoring.setEnabled(true);
- FeatureFlagStore featureFlagStore = new FeatureFlagStore();
- featureFlagStore.setEnabled(true);
- featureFlagStore.validateAndInit();
-
- FeatureFlagConfigurationSetting featureFlag = new FeatureFlagConfigurationSetting("Alpha", false);
- featureFlag.setValue("");
-
- properties.getStores().get(0).setFeatureFlags(featureFlagStore);
- properties.getStores().get(0).setMonitoring(monitoring);
-
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of(featureFlag));
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0",
- "feature_management"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void watchedKeyCheck() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
- String[] expectedSourceNames = new String[] {
- KEY_FILTER + "store1/\0"
- };
- assertEquals(expectedSourceNames.length, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void defaultFailFastThrowException() {
- when(emptyEnvironment.getActiveProfiles()).thenReturn(new String[] {});
- when(emptyEnvironment.getPropertySources()).thenReturn(sources);
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(configStoreMock.getEndpoint()).thenReturn(TEST_STORE_NAME);
- when(configStoreMock.isEnabled()).thenReturn(true);
- when(configStoreMock.getSelects()).thenReturn(List.of());
- when(configStoreMock.getFeatureFlags()).thenReturn(featureFlagStoreMock);
- when(configStoreMock.isFailFast()).thenReturn(true);
-
- AppConfigurationStoreTrigger trigger = new AppConfigurationStoreTrigger();
- AppConfigurationStoreMonitoring monitor = new AppConfigurationStoreMonitoring();
- monitor.setEnabled(true);
- monitor.setTriggers(List.of(trigger));
-
- when(configStoreMock.getMonitoring()).thenReturn(monitor);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, List.of(configStoreMock), replicaLookUpMock, featureFlagClientMock);
-
- when(replicaClientMock.getWatchKey(Mockito.any(), Mockito.anyString(), Mockito.anyBoolean())).thenThrow(new RuntimeException());
- RuntimeException e = assertThrows(RuntimeException.class, () -> locator.locate(emptyEnvironment));
- assertEquals("Failed to generate property sources for " + TEST_STORE_NAME, e.getMessage());
- verify(configStoreMock, times(1)).isFailFast();
- }
-
- @Test
- public void refreshThrowException() throws IllegalArgumentException {
- setupEmptyEnvironment();
- when(replicaClientMock.listSettings(any(), Mockito.anyBoolean())).thenThrow(new RuntimeException());
-
- AppConfigurationPropertySourceLocator.STARTUP.set(false);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.getLoadState(Mockito.anyString())).thenReturn(true);
- RuntimeException e = assertThrows(RuntimeException.class, () -> locator.locate(emptyEnvironment));
- assertEquals("Failed to generate property sources for store1", e.getMessage());
- }
- }
-
- @Test
- public void notFailFastShouldPass() {
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
- when(emptyEnvironment.getActiveProfiles()).thenReturn(new String[] {});
- when(emptyEnvironment.getPropertySources()).thenReturn(sources);
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(configStoreMock.getEndpoint()).thenReturn(TEST_STORE_NAME);
- when(configStoreMock.isEnabled()).thenReturn(true);
- when(configStoreMock.getSelects()).thenReturn(List.of());
- when(configStoreMock.getFeatureFlags()).thenReturn(featureFlagStoreMock);
- when(configStoreMock.isFailFast()).thenReturn(false);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, List.of(configStoreMock), replicaLookUpMock, featureFlagClientMock);
-
- properties.getStores().get(0).setFailFast(false);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- // Once a store fails it should stop attempting to load
- verify(configStoreMock, times(3)).isFailFast();
- }
- }
-
- @Test
- public void multiplePropertySourcesExistForMultiStores() {
- setupEmptyEnvironment();
- when(featureFlagClientMock.getProperties()).thenReturn(Map.of());
- TestUtils.addStore(properties, TEST_STORE_NAME_2, TEST_CONN_STRING_2, KEY_FILTER);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties,
- clientFactoryMock, keyVaultClientFactory, null, properties.getStores(), replicaLookUpMock,
- featureFlagClientMock);
-
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
- String[] expectedSourceNames = new String[] { KEY_FILTER + TEST_STORE_NAME_2 + "/\0",
- KEY_FILTER + TEST_STORE_NAME + "/\0" };
- assertEquals(2, sources.size());
- assertArrayEquals((Object[]) expectedSourceNames, sources.stream().map(PropertySource::getName).toArray());
- }
- }
-
- @Test
- public void awaitOnError() {
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of());
- when(appPropertiesMock.getPrekillTime()).thenReturn(5);
-
- ConfigurableEnvironment env = Mockito.mock(ConfigurableEnvironment.class);
- MutablePropertySources sources = new MutablePropertySources();
-
- sources.addFirst(new PropertySource("refreshArgs") {
-
- @Override
- public Object getProperty(String name) {
- return null;
- }
- });
-
- when(env.getPropertySources()).thenReturn(sources);
-
- String[] array = {};
- when(env.getActiveProfiles()).thenReturn(array);
- AppConfigurationKeyValueSelector selectedKeys = new AppConfigurationKeyValueSelector()
- .setKeyFilter("/application/");
- List selects = new ArrayList<>();
- selects.add(selectedKeys);
- when(configStoreMockError.getSelects()).thenReturn(selects);
- when(configStoreMockError.isEnabled()).thenReturn(true);
- when(configStoreMockError.isFailFast()).thenReturn(true);
- when(configStoreMockError.getEndpoint()).thenReturn("");
-
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenThrow(new NullPointerException(""));
- when(appPropertiesMock.getPrekillTime()).thenReturn(-60);
- when(appPropertiesMock.getStartDate()).thenReturn(Instant.now());
-
- locator = new AppConfigurationPropertySourceLocator(appPropertiesMock, clientFactoryMock, keyVaultClientFactory,
- null, List.of(configStoreMockError), replicaLookUpMock, featureFlagClientMock);
-
- assertThrows(RuntimeException.class, () -> locator.locate(env));
- verify(appPropertiesMock, times(1)).getPrekillTime();
- }
-
- @Test
- public void storeDisabled() {
- when(emptyEnvironment.getActiveProfiles()).thenReturn(new String[] {});
- when(emptyEnvironment.getPropertySources()).thenReturn(sources);
- properties.getStores().get(0).setEnabled(false);
- properties.getStores().get(0).setMonitoring(monitoring);
-
- locator = new AppConfigurationPropertySourceLocator(appProperties, clientFactoryMock, keyVaultClientFactory,
- null, stores, replicaLookUpMock, featureFlagClientMock);
- try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- stateHolderMock.when(() -> StateHolder.updateState(Mockito.any())).thenReturn(null);
- PropertySource> source = locator.locate(emptyEnvironment);
- assertTrue(source instanceof CompositePropertySource);
-
- Collection> sources = ((CompositePropertySource) source).getPropertySources();
-
- assertEquals(0, sources.size());
- }
- }
-
- private void setupEmptyEnvironment() {
- when(emptyEnvironment.getActiveProfiles()).thenReturn(new String[] {});
- when(emptyEnvironment.getPropertySources()).thenReturn(sources);
- when(clientFactoryMock.getAvailableClients(Mockito.anyString(), Mockito.eq(true))).thenReturn(Arrays.asList(replicaClientMock));
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(List.of());
- }
-}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
index ba6b62292b8c..161681f4740b 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
@@ -101,13 +101,12 @@ public void cleanup() throws Exception {
public void refreshWithoutTimeWatchKeyConfigStoreNotLoaded(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(false);
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
}
}
@@ -115,20 +114,18 @@ public void refreshWithoutTimeWatchKeyConfigStoreNotLoaded(TestInfo testInfo) {
public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNotReturned(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
List watchKeys = generateWatchKeys();
State newState = new State(watchKeys, Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
// Config Store doesn't return a watch key change.
- when(clientMock.getWatchKey(Mockito.eq(KEY_FILTER), Mockito.eq(EMPTY_LABEL), Mockito.anyBoolean()))
- .thenReturn(watchKeys.get(0));
+ when(clientMock.getWatchKey(Mockito.eq(KEY_FILTER), Mockito.eq(EMPTY_LABEL), Mockito.anyBoolean())).thenReturn(watchKeys.get(0));
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(true);
stateHolderMock.when(() -> StateHolder.getState(endpoint)).thenReturn(newState);
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
}
}
@@ -136,7 +133,6 @@ public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNotReturned(TestInfo te
public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNoChange(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
FeatureFlagState newState = new FeatureFlagState(
List.of(new FeatureFlags(new SettingSelector().setKeyFilter(KEY_FILTER).setLabelFilter(EMPTY_LABEL), null)),
@@ -147,38 +143,36 @@ public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNoChange(TestInfo testI
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
}
}
- @SuppressWarnings("try")
@Test
public void refreshWithoutTimeFeatureFlagDisabled(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
configStore.getFeatureFlags().setEnabled(false);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ stateHolderMock.verify(() -> StateHolder.getLoadState(Mockito.anyString()), times(1));
}
}
- @SuppressWarnings("try")
@Test
public void refreshWithoutTimeFeatureFlagNotLoaded(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
configStore.getFeatureFlags().setEnabled(true);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ stateHolderMock.verify(() -> StateHolder.getLoadState(Mockito.anyString()), times(1));
}
}
@@ -186,8 +180,7 @@ public void refreshWithoutTimeFeatureFlagNotLoaded(TestInfo testInfo) {
public void refreshWithoutTimeFeatureFlagNoChange(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
-
+
FeatureFlagState newState = new FeatureFlagState(
List.of(new FeatureFlags(new SettingSelector().setKeyFilter(KEY_FILTER).setLabelFilter(EMPTY_LABEL), null)),
Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
@@ -197,8 +190,8 @@ public void refreshWithoutTimeFeatureFlagNoChange(TestInfo testInfo) {
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertFalse(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
}
}
@@ -207,8 +200,7 @@ public void refreshWithoutTimeFeatureFlagNoChange(TestInfo testInfo) {
public void refreshWithoutTimeFeatureFlagEtagChanged(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
- when(clientFactoryMock.findOriginForEndpoint(Mockito.eq(endpoint))).thenReturn(endpoint);
-
+
FeatureFlags featureFlags = new FeatureFlags(new SettingSelector(), watchKeysFeatureFlags);
FeatureFlagState newState = new FeatureFlagState(List.of(featureFlags),
Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
@@ -218,8 +210,8 @@ public void refreshWithoutTimeFeatureFlagEtagChanged(TestInfo testInfo) {
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertTrue(
- AppConfigurationRefreshUtil.checkStoreAfterRefreshFailed(clientMock, clientFactoryMock, featureStore));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
+ assertTrue(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
}
}
@@ -241,8 +233,7 @@ public void refreshStoresCheckSettingsTestNotEnabled(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -261,8 +252,7 @@ public void refreshStoresCheckSettingsTestNotLoaded(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -282,8 +272,7 @@ public void refreshStoresCheckSettingsTestNotRefreshTime(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -305,8 +294,7 @@ public void refreshStoresCheckSettingsTestFailedRequest(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
assertEquals(newState, StateHolder.getState(endpoint));
}
}
@@ -332,8 +320,7 @@ public void refreshStoresCheckSettingsTestRefreshTimeNoChange(TestInfo testInfo)
assertEquals(newState, StateHolder.getState(endpoint));
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -349,8 +336,7 @@ public void refreshStoresCheckSettingsTestTriggerRefresh(TestInfo testInfo) {
ConfigurationSetting refreshKey = new ConfigurationSetting().setKey(KEY_FILTER).setLabel(EMPTY_LABEL)
.setETag("new");
- when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean()))
- .thenReturn(refreshKey);
+ when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean())).thenReturn(refreshKey);
State newState = new State(generateWatchKeys(), Math.toIntExact(Duration.ofMinutes(-1).getSeconds()), endpoint);
@@ -364,8 +350,7 @@ public void refreshStoresCheckSettingsTestTriggerRefresh(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertTrue(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
verify(currentStateMock, times(1)).updateStateRefresh(Mockito.any(), Mockito.any());
}
}
@@ -388,8 +373,7 @@ public void refreshStoresCheckFeatureFlagTestNotLoaded(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -411,8 +395,7 @@ public void refreshStoresCheckFeatureFlagTestNotRefreshTime(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
@@ -440,8 +423,7 @@ public void refreshStoresCheckFeatureFlagTestNoChange(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
verify(currentStateMock, times(1)).updateFeatureFlagStateRefresh(Mockito.any(), Mockito.any());
}
@@ -468,8 +450,7 @@ public void refreshStoresCheckFeatureFlagTestTriggerRefresh(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertTrue(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
- Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientBuilderTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientBuilderTest.java
index 3dda6cd72a43..c35010596f36 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientBuilderTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientBuilderTest.java
@@ -51,7 +51,7 @@ public class AppConfigurationReplicaClientBuilderTest {
@Mock
private ConfigurationClientBuilderFactory clientFactoryMock;
-
+
@Mock
private Environment envMock;
@@ -68,7 +68,6 @@ public void setup() {
configStore.validateAndInit();
clientBuilder = null;
- when(envMock.getActiveProfiles()).thenReturn(new String[0]);
}
@AfterEach
@@ -79,8 +78,7 @@ public void cleanup() throws Exception {
@Test
public void buildClientFromEndpointTest() {
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
when(clientFactoryMock.build()).thenReturn(builderMock);
@@ -102,8 +100,7 @@ public void buildClientFromConnectionStringTest() {
configStore.setConnectionString(TEST_CONN_STRING);
configStore.validateAndInit();
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
when(clientFactoryMock.build()).thenReturn(builderMock);
@@ -120,9 +117,7 @@ public void buildClientFromConnectionStringTest() {
@Test
public void modifyClientTest() {
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setClientProvider(modifierMock);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, modifierMock, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
@@ -153,8 +148,7 @@ public void buildClientsFromMultipleEndpointsTest() {
configStore.validateAndInit();
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
@@ -180,8 +174,7 @@ public void buildClientsFromMultipleConnectionStringsTest() {
configStore.validateAndInit();
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
@@ -205,8 +198,7 @@ public void endpointAndConnectionString() {
configStore.setConnectionString(TEST_CONN_STRING);
configStore.validateAndInit();
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
String message = assertThrows(IllegalArgumentException.class,
() -> clientBuilder.buildClients(configStore).get(0)).getMessage();
@@ -216,8 +208,7 @@ public void endpointAndConnectionString() {
@Test
public void buildClientTest() {
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
when(clientFactoryMock.build()).thenReturn(builderMock);
@@ -236,8 +227,7 @@ public void buildClientTest() {
@Test
public void buildClientConnectionStringTest() {
configStore.setConnectionString(TEST_CONN_STRING);
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
when(clientFactoryMock.build()).thenReturn(builderMock);
@@ -255,8 +245,7 @@ public void buildClientConnectionStringTest() {
@Test
public void buildClientConnectionStringsTest() {
configStore.setConnectionStrings(List.of(TEST_CONN_STRING, TEST_CONN_STRING_GEO));
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
when(builderMock.addPolicy(Mockito.any())).thenReturn(builderMock);
@@ -274,8 +263,7 @@ public void buildClientConnectionStringsTest() {
@Test
public void buildClientConnectionStringInvalidTest() {
configStore.setConnectionString(TEST_CONN_STRING);
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
@@ -287,8 +275,7 @@ public void buildClientConnectionStringInvalidTest() {
@Test
public void buildClientConnectionStringInvalid2Test() {
configStore.setConnectionString("Not A Connection String");
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
@@ -300,8 +287,7 @@ public void buildClientConnectionStringInvalid2Test() {
@Test
public void buildClientConnectionStringInvalid3Test() {
configStore.setConnectionString("Not;A;Connection String");
- clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, false);
- clientBuilder.setEnvironment(envMock);
+ clientBuilder = new AppConfigurationReplicaClientsBuilder(0, clientFactoryMock, null, false, false);
AppConfigurationReplicaClientsBuilder spy = Mockito.spy(clientBuilder);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientFactoryTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientFactoryTest.java
index 3be2260be0f4..186a6ee8db94 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientFactoryTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientFactoryTest.java
@@ -3,8 +3,6 @@
package com.azure.spring.cloud.appconfiguration.config.implementation;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
@@ -79,12 +77,4 @@ public void findOriginTest() {
assertEquals(invalidReplica, clientFactory.findOriginForEndpoint(invalidReplica));
}
- @Test
- public void hasReplicasTest() {
- assertTrue(clientFactory.hasReplicas(originEndpoint));
- assertTrue(clientFactory.hasReplicas(replica1));
- assertFalse(clientFactory.hasReplicas(invalidReplica));
- assertFalse(clientFactory.hasReplicas(noReplicaEndpoint));
- }
-
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
index 99c71ea169a0..9e328cffd751 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
@@ -37,7 +37,6 @@
import com.azure.core.http.rest.PagedResponse;
import com.azure.core.http.rest.PagedResponseBase;
import com.azure.core.http.rest.Response;
-import com.azure.core.util.Configuration;
import com.azure.data.appconfiguration.ConfigurationClient;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.ConfigurationSnapshot;
@@ -45,7 +44,6 @@
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.data.appconfiguration.models.SnapshotComposition;
import com.azure.identity.CredentialUnavailableException;
-import com.azure.spring.cloud.appconfiguration.config.implementation.http.policy.TracingInfo;
import reactor.core.publisher.Mono;
@@ -67,7 +65,10 @@ public class AppConfigurationReplicaClientTest {
private Supplier>> supplierMock;
@Mock
- private Response mockResponse;
+ private Response configurationSettingResponse;
+
+ @Mock
+ private Response snapshotResponseMock;
private final String endpoint = "clientTest.azconfig.io";
@@ -87,20 +88,17 @@ public void cleanup() throws Exception {
@Test
public void getWatchKeyTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
ConfigurationSetting watchKey = new ConfigurationSetting().setKey("watch").setLabel("\0");
- when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.isNull(), Mockito.anyBoolean(),
- Mockito.any())).thenReturn(mockResponse);
- when(mockResponse.getValue()).thenReturn(watchKey);
+ when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.any(), Mockito.anyBoolean(),
+ Mockito.any())).thenReturn(configurationSettingResponse);
+ when(configurationSettingResponse.getValue()).thenReturn(watchKey);
- //assertEquals(watchKey, client.getWatchKey("watch", "\0", false));
+ assertEquals(watchKey, client.getWatchKey("watch", "\0", false));
- when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.isNull(), Mockito.anyBoolean(),
- Mockito.any())).thenReturn(mockResponse);
- when(mockResponse.getValue()).thenThrow(exceptionMock);
+ when(configurationSettingResponse.getValue()).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
@@ -114,15 +112,14 @@ public void getWatchKeyTest() {
when(responseMock.getStatusCode()).thenReturn(499);
assertThrows(HttpResponseException.class, () -> client.getWatchKey("watch", "\0", false));
- when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.isNull(), Mockito.anyBoolean(),
+ when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.any(), Mockito.anyBoolean(),
Mockito.any())).thenThrow(new UncheckedIOException(new UnknownHostException()));
assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
}
@Test
public void listSettingsTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
ConfigurationSetting configurationSetting = new ConfigurationSetting().setKey("test-key");
List configurations = List.of(configurationSetting);
@@ -154,8 +151,7 @@ public void listSettingsTest() {
@Test
public void listFeatureFlagsTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
FeatureFlagConfigurationSetting featureFlag = new FeatureFlagConfigurationSetting("Alpha", false);
List configurations = List.of(featureFlag);
@@ -192,8 +188,7 @@ public void listFeatureFlagsTest() {
@Test
public void listSettingsUnknownHostTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenThrow(new UncheckedIOException(new UnknownHostException()));
@@ -202,8 +197,7 @@ public void listSettingsUnknownHostTest() {
@Test
public void listSettingsNoCredentialTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenThrow(new CredentialUnavailableException("No Credential"));
@@ -213,12 +207,9 @@ public void listSettingsNoCredentialTest() {
@Test
public void getWatchNoCredentialTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
- when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.isNull(), Mockito.anyBoolean(),
- Mockito.any())).thenReturn(mockResponse);
- when(mockResponse.getValue())
+ when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()))
.thenThrow(new CredentialUnavailableException("No Credential"));
assertThrows(CredentialUnavailableException.class, () -> client.getWatchKey("key", "label", false));
@@ -226,8 +217,7 @@ public void getWatchNoCredentialTest() {
@Test
public void backoffTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
// Setups in the past and with no errors.
assertTrue(client.getBackoffEndTime().isBefore(Instant.now()));
@@ -255,55 +245,57 @@ public void backoffTest() {
@Test
public void listSettingSnapshotTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
List configurations = new ArrayList<>();
ConfigurationSnapshot snapshot = new ConfigurationSnapshot(null);
snapshot.setSnapshotComposition(SnapshotComposition.KEY);
- when(clientMock.getSnapshot(Mockito.any())).thenReturn(snapshot);
+ when(clientMock.getSnapshotWithResponse(Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenReturn(snapshotResponseMock);
+ when(snapshotResponseMock.getValue()).thenReturn(snapshot);
when(clientMock.listConfigurationSettingsForSnapshot(Mockito.any())).thenReturn(settingsMock);
- assertEquals(configurations, client.listSettingSnapshot("SnapshotName"));
+ assertEquals(configurations, client.listSettingSnapshot("SnapshotName", false));
when(clientMock.listConfigurationSettingsForSnapshot(Mockito.any())).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName"));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
when(responseMock.getStatusCode()).thenReturn(408);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName"));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
when(responseMock.getStatusCode()).thenReturn(500);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName"));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
when(responseMock.getStatusCode()).thenReturn(499);
- assertThrows(HttpResponseException.class, () -> client.listSettingSnapshot("SnapshotName"));
+ assertThrows(HttpResponseException.class, () -> client.listSettingSnapshot("SnapshotName", false));
- when(clientMock.getSnapshot(Mockito.any())).thenThrow(new UncheckedIOException(new UnknownHostException()));
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName"));
+ when(clientMock.getSnapshotWithResponse(Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenThrow(new UncheckedIOException(new UnknownHostException()));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
}
@Test
public void listSettingSnapshotInvalidCompositionTypeTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
ConfigurationSnapshot snapshot = new ConfigurationSnapshot(null);
snapshot.setSnapshotComposition(SnapshotComposition.KEY_LABEL);
- when(clientMock.getSnapshot(Mockito.any())).thenReturn(snapshot);
+ when(clientMock.getSnapshotWithResponse(Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenReturn(snapshotResponseMock);
+ when(snapshotResponseMock.getValue()).thenReturn(snapshot);
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> client.listSettingSnapshot("SnapshotName"));
+ () -> client.listSettingSnapshot("SnapshotName", false));
assertEquals("Snapshot SnapshotName needs to be of type Key.", e.getMessage());
}
@Test
public void updateSyncTokenTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
String fakeToken = "fake_sync_token";
client.updateSyncToken(fakeToken);
@@ -316,8 +308,7 @@ public void updateSyncTokenTest() {
@Test
public void checkWatchKeysTest() {
- AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock,
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ AppConfigurationReplicaClient client = new AppConfigurationReplicaClient(endpoint, clientMock);
FeatureFlagConfigurationSetting featureFlag = new FeatureFlagConfigurationSetting("Alpha", false);
List configurations = List.of(featureFlag);
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManagerTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManagerTest.java
index ff6a30fed45b..bdaa51cecaf7 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManagerTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/ConnectionManagerTest.java
@@ -4,7 +4,6 @@
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_ENDPOINT;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -118,8 +117,6 @@ public void backoffTest() {
expectedEndpoints.add(replicaEndpoint);
assertEquals(2, connectionManager.getAvailableClients().size());
- assertEquals(2, connectionManager.getAllEndpoints().size());
- assertTrue(connectionManager.getAllEndpoints().containsAll(expectedEndpoints));
assertEquals(AppConfigurationStoreHealth.UP, connectionManager.getHealth());
connectionManager.backoffClient(originEndpoint);
@@ -129,15 +126,11 @@ public void backoffTest() {
when(replicaClient1.getBackoffEndTime()).thenReturn(Instant.now().plusSeconds(1000));
assertEquals(1, connectionManager.getAvailableClients().size());
- assertEquals(2, connectionManager.getAllEndpoints().size());
- assertTrue(connectionManager.getAllEndpoints().containsAll(expectedEndpoints));
assertEquals(AppConfigurationStoreHealth.UP, connectionManager.getHealth());
connectionManager.backoffClient(originEndpoint);
assertEquals(1, connectionManager.getAvailableClients().size());
- assertEquals(2, connectionManager.getAllEndpoints().size());
- assertTrue(connectionManager.getAllEndpoints().containsAll(expectedEndpoints));
assertEquals(AppConfigurationStoreHealth.UP, connectionManager.getHealth());
connectionManager.backoffClient(replicaEndpoint);
@@ -146,8 +139,6 @@ public void backoffTest() {
when(replicaClient2.getBackoffEndTime()).thenReturn(Instant.now().plusSeconds(1000));
assertEquals(0, connectionManager.getAvailableClients().size());
- assertEquals(2, connectionManager.getAllEndpoints().size());
- assertTrue(connectionManager.getAllEndpoints().containsAll(expectedEndpoints));
assertEquals(AppConfigurationStoreHealth.DOWN, connectionManager.getHealth());
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
index f473388bd8c3..07c2e5a56fa2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
@@ -2,18 +2,18 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.DEFAULT_ROLLOUT_PERCENTAGE;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.E_TAG;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_CONTENT_TYPE;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_ID;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_REFERENCE;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.GROUPS;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.USERS;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.DEFAULT_ROLLOUT_PERCENTAGE;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FEATURE_LABEL;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FEATURE_VALUE_ALL;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FEATURE_VALUE_TELEMETRY;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.GROUPS;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_ENDPOINT;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_E_TAG;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FEATURE_VALUE_ALL;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.USERS;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestUtils.createItemFeatureFlag;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -78,8 +78,7 @@ public void loadFeatureFlagsTestNoFeatureFlags() {
FeatureFlags featureFlags = new FeatureFlags(null, settings);
when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList,
- false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals("FakeKey", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -93,8 +92,7 @@ public void loadFeatureFlagsTestFeatureFlags() {
FeatureFlags featureFlags = new FeatureFlags(null, settings);
when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList,
- false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/Alpha", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -109,8 +107,7 @@ public void loadFeatureFlagsTestMultipleLoads() {
FeatureFlags featureFlags = new FeatureFlags(null, settings);
when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList,
- false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/Alpha", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -168,8 +165,7 @@ public void loadFeatureFlagsTestTargetingFilter() {
FeatureFlags featureFlags = new FeatureFlags(null, settings);
when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList,
- false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/TargetingTest", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
index 184a33374bb7..3b10d736f8ec 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
@@ -180,7 +180,7 @@ private void updateNextRefreshBackoffCalcTest(TestInfo testInfo) {
private void loadStateTest(TestInfo testInfo) {
String endpoint = testInfo.getDisplayName() + "updateRefreshTimeBackoffCalc" + ".azconfig.io";
StateHolder testStateHolder = new StateHolder();
- testStateHolder.setLoadState(endpoint, true, false);
+ testStateHolder.setLoadState(endpoint, true);
StateHolder.updateState(testStateHolder);
assertEquals(testStateHolder.getLoadState().get(endpoint), StateHolder.getLoadState(endpoint));
assertEquals(testStateHolder, StateHolder.getCurrentState());
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/TestConstants.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/TestConstants.java
index 63903d1ef5ec..48e379747e3b 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/TestConstants.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/TestConstants.java
@@ -6,6 +6,22 @@
* Test constants which can be shared across different test classes
*/
public final class TestConstants {
+
+ /**
+ * Http Header Correlation Context
+ */
+ public static final String CORRELATION_CONTEXT = "Correlation-Context";
+
+ /**
+ * App Configurations Key Vault Reference Content Type
+ */
+ public static final String KEY_VAULT_CONTENT_TYPE = "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8";
+
+ public static final String USERS = "users";
+
+ public static final String GROUPS = "groups";
+
+ public static final String DEFAULT_ROLLOUT_PERCENTAGE = "defaultRolloutPercentage";
// Store specific configuration
public static final String TEST_STORE_NAME = "store1";
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfigurationTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfigurationTest.java
deleted file mode 100644
index 93202173f069..000000000000
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/config/AppConfigurationBootstrapConfigurationTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.appconfiguration.config.implementation.config;
-
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.CONN_STRING_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FAIL_FAST_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.STORE_ENDPOINT_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_CONN_STRING;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_STORE_NAME;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestUtils.propPair;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.autoconfigure.AutoConfigurations;
-import org.springframework.boot.test.context.runner.ApplicationContextRunner;
-
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationPropertySourceLocator;
-import com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationReplicaClientFactory;
-import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
-
-public class AppConfigurationBootstrapConfigurationTest {
-
- private static final ApplicationContextRunner CONTEXT_RUNNER = new ApplicationContextRunner()
- .withConfiguration(AutoConfigurations.of(AppConfigurationBootstrapConfiguration.class,
- AzureGlobalPropertiesAutoConfiguration.class))
- .withPropertyValues(propPair("spring.cloud.azure.appconfiguration.enabled", "true"));
-
- @Test
- public void iniConnectionStringSystemAssigned() {
- CONTEXT_RUNNER
- .withPropertyValues(propPair(STORE_ENDPOINT_PROP, TEST_STORE_NAME), propPair(FAIL_FAST_PROP, "false"))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationPropertySourceLocator.class));
- }
-
- @Test
- public void iniConnectionStringUserAssigned() {
- CONTEXT_RUNNER
- .withPropertyValues(propPair(STORE_ENDPOINT_PROP, TEST_STORE_NAME), propPair(FAIL_FAST_PROP, "false"),
- propPair("spring.cloud.azure.appconfiguration.managed-identity.client-id", "client-id"))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationPropertySourceLocator.class));
- }
-
- @Test
- public void propertySourceLocatorBeanCreated() {
- CONTEXT_RUNNER
- .withPropertyValues(propPair(CONN_STRING_PROP, TEST_CONN_STRING), propPair(FAIL_FAST_PROP, "false"))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationPropertySourceLocator.class));
- }
-
- @Test
- public void clientsBeanCreated() {
- CONTEXT_RUNNER
- .withPropertyValues(propPair(CONN_STRING_PROP, TEST_CONN_STRING))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationReplicaClientFactory.class));
- }
-}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicyTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicyTest.java
index c08c29975ffa..d3bfdea27cef 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicyTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicyTest.java
@@ -2,10 +2,7 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation.http.policy;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.CORRELATION_CONTEXT;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.DEV_ENV_TRACING;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.KEY_VAULT_CONFIGURED_TRACING;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.USER_AGENT_TYPE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@@ -24,11 +21,13 @@
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
+import com.azure.core.http.HttpHeaderName;
import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpPipelineCallContext;
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpRequest;
import com.azure.core.util.Configuration;
+import com.azure.spring.cloud.appconfiguration.config.implementation.RequestTracingConstants;
// This test class needs to be isolated and ran sequential as it uses BaseAppConfigurationPolicy.setWatchRequests
// which mutates a global static and can result in race condition failures.
@@ -55,27 +54,28 @@ public void cleanup() throws Exception {
MockitoAnnotations.openMocks(this).close();
}
- @SuppressWarnings("deprecation")
@Test
public void startupThenWatchUpdateTest() throws MalformedURLException {
URL url = new URL("https://www.test.url/kv");
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
+ request.setHeader(HttpHeaderName.USER_AGENT, "PreExistingUserAgent");
BaseAppConfigurationPolicy policy = new BaseAppConfigurationPolicy(
- new TracingInfo(false, false, 0, Configuration.getGlobalConfiguration()));
+ new TracingInfo(false, 0, Configuration.getGlobalConfiguration()));
when(contextMock.getHttpRequest()).thenReturn(request);
policy.process(contextMock, nextMock);
- String userAgent = contextMock.getHttpRequest().getHeaders().get(USER_AGENT_TYPE).getValue();
+ String userAgent = contextMock.getHttpRequest().getHeaders().get(HttpHeaderName.USER_AGENT).getValue();
assertEquals("null/null " + PRE_USER_AGENT, userAgent);
assertEquals("RequestType=Startup",
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
+ contextMock.getHttpRequest().getHeaders()
+ .get(HttpHeaderName.fromString(RequestTracingConstants.CORRELATION_CONTEXT_HEADER.toString()))
+ .getValue());
request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
+ request.setHeader(HttpHeaderName.USER_AGENT, "PreExistingUserAgent");
when(contextMock.getHttpRequest()).thenReturn(request);
when(contextMock.getData("refresh")).thenReturn(Optional.of(true));
@@ -85,10 +85,12 @@ public void startupThenWatchUpdateTest() throws MalformedURLException {
assertEquals("null/null " + PRE_USER_AGENT, userAgent);
assertEquals("RequestType=Watch",
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
+ contextMock.getHttpRequest().getHeaders()
+ .get(HttpHeaderName.fromString(RequestTracingConstants.CORRELATION_CONTEXT_HEADER.toString()))
+ .getValue());
request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
+ request.setHeader(HttpHeaderName.USER_AGENT, "PreExistingUserAgent");
when(contextMock.getHttpRequest()).thenReturn(request);
@@ -96,55 +98,26 @@ public void startupThenWatchUpdateTest() throws MalformedURLException {
assertEquals("null/null " + PRE_USER_AGENT, userAgent);
assertEquals("RequestType=Watch",
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
+ contextMock.getHttpRequest().getHeaders()
+ .get(HttpHeaderName.fromString(RequestTracingConstants.CORRELATION_CONTEXT_HEADER.toString()))
+ .getValue());
}
- @SuppressWarnings("deprecation")
- @Test
- public void devIsConfigured() throws MalformedURLException {
- BaseAppConfigurationPolicy policy = new BaseAppConfigurationPolicy(
- new TracingInfo(true, false, 0, Configuration.getGlobalConfiguration()));
-
- URL url = new URL("https://www.test.url/kv");
- HttpRequest request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
- when(contextMock.getHttpRequest()).thenReturn(request);
-
- policy.process(contextMock, nextMock);
- assertEquals("RequestType=Startup,Env=" + DEV_ENV_TRACING,
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
- }
-
- @SuppressWarnings("deprecation")
@Test
public void keyVaultIsConfigured() throws MalformedURLException {
BaseAppConfigurationPolicy policy = new BaseAppConfigurationPolicy(
- new TracingInfo(false, true, 0, Configuration.getGlobalConfiguration()));
+ new TracingInfo(true, 0, Configuration.getGlobalConfiguration()));
URL url = new URL("https://www.test.url/kv");
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
+ request.setHeader(HttpHeaderName.USER_AGENT, "PreExistingUserAgent");
when(contextMock.getHttpRequest()).thenReturn(request);
policy.process(contextMock, nextMock);
assertEquals("RequestType=Startup," + KEY_VAULT_CONFIGURED_TRACING,
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
- }
-
- @SuppressWarnings("deprecation")
- @Test
- public void devAndKeyVaultAreConfigured() throws MalformedURLException {
- BaseAppConfigurationPolicy policy = new BaseAppConfigurationPolicy(
- new TracingInfo(true, true, 0, Configuration.getGlobalConfiguration()));
-
- URL url = new URL("https://www.test.url/kv");
- HttpRequest request = new HttpRequest(HttpMethod.GET, url);
- request.setHeader(USER_AGENT_TYPE, "PreExistingUserAgent");
- when(contextMock.getHttpRequest()).thenReturn(request);
-
- policy.process(contextMock, nextMock);
- assertEquals("RequestType=Startup,Env=" + DEV_ENV_TRACING + "," + KEY_VAULT_CONFIGURED_TRACING,
- contextMock.getHttpRequest().getHeaders().get(CORRELATION_CONTEXT).getValue());
+ contextMock.getHttpRequest().getHeaders()
+ .get(HttpHeaderName.fromString(RequestTracingConstants.CORRELATION_CONTEXT_HEADER.toString()))
+ .getValue());
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
index 67c3aba02153..2bdcdd238590 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
@@ -23,20 +23,17 @@ public class TracingInfoTest {
public void getValueTest() {
Configuration configuration = getConfiguration("false");
- TracingInfo tracingInfo = new TracingInfo(false, false, 0, configuration);
+ TracingInfo tracingInfo = new TracingInfo(false, 0, configuration);
assertEquals("RequestType=Startup", tracingInfo.getValue(false));
assertEquals("RequestType=Watch", tracingInfo.getValue(true));
- tracingInfo = new TracingInfo(true, false, 0, configuration);
- assertEquals("RequestType=Startup,Env=Dev", tracingInfo.getValue(false));
-
- tracingInfo = new TracingInfo(false, true, 0, configuration);
+ tracingInfo = new TracingInfo(true, 0, configuration);
assertEquals("RequestType=Startup,UsesKeyVault", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 1, configuration);
+ tracingInfo = new TracingInfo(false, 1, configuration);
assertEquals("RequestType=Startup,ReplicaCount=1", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 0, configuration);
+ tracingInfo = new TracingInfo(false, 0, configuration);
tracingInfo.getFeatureFlagTracing().updateFeatureFilterTelemetry("Random");
assertEquals("RequestType=Startup,Filter=CSTM", tracingInfo.getValue(false));
@@ -44,19 +41,19 @@ public void getValueTest() {
@Test
public void disableTracingTest() {
- TracingInfo tracingInfo = new TracingInfo(false, false, 0, getConfiguration(null));
+ TracingInfo tracingInfo = new TracingInfo(false, 0, getConfiguration(null));
assertNotEquals("", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 0, getConfiguration(""));
+ tracingInfo = new TracingInfo(false, 0, getConfiguration(""));
assertNotEquals("", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 0, getConfiguration("true"));
+ tracingInfo = new TracingInfo(false, 0, getConfiguration("true"));
assertEquals("", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 0, getConfiguration("false"));
+ tracingInfo = new TracingInfo(false, 0, getConfiguration("false"));
assertNotEquals("", tracingInfo.getValue(false));
- tracingInfo = new TracingInfo(false, false, 0, getConfiguration("random string"));
+ tracingInfo = new TracingInfo(false, 0, getConfiguration("random string"));
assertNotEquals("", tracingInfo.getValue(false));
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationPropertiesTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationPropertiesTest.java
index e8d839e22faa..6641e1ee7108 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationPropertiesTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/properties/AppConfigurationPropertiesTest.java
@@ -2,35 +2,18 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation.properties;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationReplicaClientsBuilder.ENDPOINT_ERR_MSG;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.CONN_STRING_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.CONN_STRING_PROP_NEW;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.FAIL_FAST_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.KEY_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.LABEL_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.REFRESH_INTERVAL_PROP;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.STORE_ENDPOINT_PROP;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_CONN_STRING;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_ENDPOINT;
import static com.azure.spring.cloud.appconfiguration.config.implementation.TestConstants.TEST_ENDPOINT_GEO;
-import static com.azure.spring.cloud.appconfiguration.config.implementation.TestUtils.propPair;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.MockitoAnnotations;
-import org.springframework.boot.autoconfigure.AutoConfigurations;
-import org.springframework.boot.test.context.runner.ApplicationContextRunner;
-
-import com.azure.spring.cloud.appconfiguration.config.implementation.config.AppConfigurationBootstrapConfiguration;
-import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
public class AppConfigurationPropertiesTest {
@@ -43,29 +26,21 @@ public class AppConfigurationPropertiesTest {
private static final String VALID_KEY = "/application/";
private static final String ILLEGAL_LABELS = "*,my-label";
+
+ private AppConfigurationProperties properties;
- @InjectMocks
- private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
- .withConfiguration(AutoConfigurations.of(AppConfigurationBootstrapConfiguration.class,
- AzureGlobalPropertiesAutoConfiguration.class))
- .withPropertyValues("spring.cloud.azure.appconfiguration.endpoint=https://test-appconfig.azconfig.io");
@BeforeEach
public void setup() {
- MockitoAnnotations.openMocks(this);
- }
-
- @AfterEach
- public void cleanup() throws Exception {
- MockitoAnnotations.openMocks(this).close();
+ properties = new AppConfigurationProperties();
+ properties.setStores(List.of(new ConfigStore()));
}
@Test
public void validInputShouldCreatePropertiesBean() {
- this.contextRunner
- .withPropertyValues(propPair(CONN_STRING_PROP, TEST_CONN_STRING))
- .withPropertyValues(propPair(FAIL_FAST_PROP, "false"))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationProperties.class));
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(TEST_CONN_STRING);
+ store.validateAndInit();
}
@Test
@@ -84,55 +59,54 @@ public void secretMustExistInConnectionString() {
}
private void testConnStringFields(String connString) {
- this.contextRunner
- .withPropertyValues(propPair(CONN_STRING_PROP, connString))
- .run(context -> assertThat(context).getFailure().hasStackTraceContaining(ENDPOINT_ERR_MSG));
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(connString);
+ IllegalStateException e = assertThrows(IllegalStateException.class, () -> properties.validateAndInit());
+ assertEquals("Connection string does not follow format Endpoint=([^;]+);Id=([^;]+);Secret=([^;]+).", e.getMessage());
}
@Test
public void asteriskShouldNotBeIncludedInTheLabels() {
- this.contextRunner
- .withPropertyValues(
- propPair(CONN_STRING_PROP, TEST_CONN_STRING),
- propPair(KEY_PROP, VALID_KEY),
- propPair(LABEL_PROP, ILLEGAL_LABELS))
- .run(context -> assertThat(context)
- .getFailure()
- .hasStackTraceContaining("LabelFilter must not contain asterisk(*)"));
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(TEST_CONN_STRING);
+ AppConfigurationKeyValueSelector select = new AppConfigurationKeyValueSelector();
+ select.setKeyFilter(VALID_KEY);
+ select.setLabelFilter(ILLEGAL_LABELS);
+ store.setSelects(List.of(select));
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> properties.validateAndInit());
+ assertEquals("LabelFilter must not contain asterisk(*)", e.getMessage());
}
@Test
public void storeNameCanBeInitIfConnectionStringConfigured() {
- this.contextRunner
- .withPropertyValues(
- propPair(CONN_STRING_PROP, TEST_CONN_STRING),
- propPair(STORE_ENDPOINT_PROP, ""))
- .withPropertyValues(propPair(FAIL_FAST_PROP, "false"))
- .run(context -> {
- AppConfigurationProperties properties = context.getBean(AppConfigurationProperties.class);
- assertThat(properties.getStores()).isNotNull();
- assertThat(properties.getStores().size()).isEqualTo(1);
- assertThat(properties.getStores().get(0).getEndpoint()).isEqualTo("https://fake.test.config.io");
- });
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(TEST_CONN_STRING);
+ store.setEndpoint("");
+ store.validateAndInit();
+ assertEquals(1, properties.getStores().size());
+ assertEquals("https://fake.test.config.io", properties.getStores().get(0).getEndpoint());
}
@Test
public void duplicateConnectionStringIsNotAllowed() {
- this.contextRunner
- .withPropertyValues(
- propPair(CONN_STRING_PROP, TEST_CONN_STRING),
- propPair(CONN_STRING_PROP_NEW, TEST_CONN_STRING))
- .run(context -> assertThat(context)
- .getFailure()
- .hasStackTraceContaining("Duplicate store name exists"));
+ properties = new AppConfigurationProperties();
+ properties.setStores(List.of(new ConfigStore(), new ConfigStore()));
+
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(TEST_CONN_STRING);
+ ConfigStore newStore = properties.getStores().get(1);
+ newStore.setConnectionString(TEST_CONN_STRING);
+
+ java.lang.IllegalArgumentException e = assertThrows(java.lang.IllegalArgumentException.class, () -> properties.validateAndInit());
+ assertEquals("Duplicate store name exists.", e.getMessage());
}
@Test
public void minValidWatchTime() {
- this.contextRunner
- .withPropertyValues(propPair(CONN_STRING_PROP, TEST_CONN_STRING))
- .withPropertyValues(propPair(REFRESH_INTERVAL_PROP, "1s"))
- .run(context -> assertThat(context).hasSingleBean(AppConfigurationProperties.class));
+ ConfigStore store = properties.getStores().get(0);
+ store.setConnectionString(TEST_CONN_STRING);
+ properties.setRefreshInterval(Duration.ofSeconds(1));
+ properties.validateAndInit();
}
@Test
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java
deleted file mode 100644
index 35d43967f85e..000000000000
--- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.azure.spring.cloud.feature.management;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.stream.Stream;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationContext;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-
-import com.azure.spring.cloud.feature.management.filters.TargetingFilter;
-import com.azure.spring.cloud.feature.management.filters.TargetingFilterTestContextAccessor;
-import com.azure.spring.cloud.feature.management.filters.TimeWindowFilter;
-import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
-import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
-import com.azure.spring.cloud.feature.management.validationstests.models.ValidationTestCase;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-import com.fasterxml.jackson.databind.type.CollectionType;
-import com.fasterxml.jackson.databind.type.TypeFactory;
-
-@ExtendWith(SpringExtension.class)
-public class ValidationsTest {
- @Mock
- private ApplicationContext context;
-
- @Mock
- private FeatureManagementConfigProperties configProperties;
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ValidationsTest.class);
-
- private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
- .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();
-
- private static final String TEST_CASE_FOLDER_PATH = "validations-tests";
-
- private final String inputsUser = "user";
-
- private final String inputsGroups = "groups";
-
- private static final String SAMPLE_FILE_NAME_FILTER = "sample";
-
- private static final String TESTS_FILE_NAME_FILTER = "tests";
-
- @BeforeEach
- public void setup() {
- MockitoAnnotations.openMocks(this);
- when(configProperties.isFailFast()).thenReturn(true);
- when(context.getBean(Mockito.contains("TimeWindow"))).thenReturn(new TimeWindowFilter());
- }
-
- @AfterEach
- public void cleanup() throws Exception {
- MockitoAnnotations.openMocks(this).close();
- }
-
- private boolean hasException(ValidationTestCase testCase) {
- final String exceptionStr = testCase.getIsEnabled().getException();
- return exceptionStr != null && !exceptionStr.isEmpty();
- }
-
- private boolean hasInput(ValidationTestCase testCase) {
- final LinkedHashMap inputsMap = testCase.getInputs();
- return inputsMap != null && !inputsMap.isEmpty();
- }
-
- private static File[] getFileList(String fileNameFilter) {
- final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(TEST_CASE_FOLDER_PATH);
- assert folderUrl != null;
-
- final File folderFile = new File(folderUrl.getFile());
- final File[] filteredFiles = folderFile
- .listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter));
- assert filteredFiles != null;
-
- Arrays.sort(filteredFiles, Comparator.comparing(File::getName));
- return filteredFiles;
- }
-
- private List readTestcasesFromFile(File testFile) throws IOException {
- final String jsonString = Files.readString(testFile.toPath());
- final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class,
- ValidationTestCase.class);
- return OBJECT_MAPPER.readValue(jsonString, typeReference);
- }
-
- @SuppressWarnings("unchecked")
- private static LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException {
- final String jsonString = Files.readString(sampleFile.toPath());
- final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() {
- });
- final Object featureManagementSection = configurations.get("feature_management");
- if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) {
- return (LinkedHashMap) featureManagementSection;
- }
- throw new IllegalArgumentException("feature_management part is not a map");
- }
-
- static Stream testProvider() throws IOException {
- List arguments = new ArrayList<>();
- File[] files = getFileList(TESTS_FILE_NAME_FILTER);
-
- final File[] sampleFiles = getFileList(SAMPLE_FILE_NAME_FILTER);
- List properties = new ArrayList<>();
- for (File sampleFile : sampleFiles) {
- final FeatureManagementProperties managementProperties = new FeatureManagementProperties();
- managementProperties.putAll(readConfigurationFromFile(sampleFile));
- properties.add(managementProperties);
- }
-
- for (int i = 0; i < files.length; i++) {
- if (files[i].getName().contains(("TargetingFilter"))) {
- continue; // TODO(mametcal). Not run the test case until we release the little endian fix
- }
- arguments.add(Arguments.of(files[i].getName(), files[i], properties.get(i)));
- }
-
- return arguments.stream();
- }
-
- @ParameterizedTest(name = "{0}")
- @MethodSource("testProvider")
- void validationTest(String name, File testsFile, FeatureManagementProperties managementProperties)
- throws IOException {
- LOGGER.debug("Running test case from file: " + name);
- final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties);
- List testCases = readTestcasesFromFile(testsFile);
- for (ValidationTestCase testCase : testCases) {
- LOGGER.debug("Test case : " + testCase.getDescription());
- if (hasException(testCase)) { // TODO(mametcal). Currently we didn't throw the exception when parameter is
- // invalid
- assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName()));
- continue;
- }
- if (hasInput(testCase)) { // Set inputs
- final Object userObj = testCase.getInputs().get(inputsUser);
- final Object groupsObj = testCase.getInputs().get(inputsGroups);
- final String user = userObj != null ? userObj.toString() : null;
- @SuppressWarnings("unchecked")
- final List groups = groupsObj != null ? (List) groupsObj : null;
- when(context.getBean(Mockito.contains("Targeting")))
- .thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups)));
- }
-
- final Boolean result = featureManager.isEnabled(testCase.getFeatureFlagName());
- assertEquals(result.toString(), testCase.getIsEnabled().getResult());
- }
- }
-}
From a6d8ed1c7d77e2c8a1dbbaa98812c4bb8ba4eecb Mon Sep 17 00:00:00 2001
From: Matthew Metcalf
Date: Mon, 3 Feb 2025 11:56:46 -0800
Subject: [PATCH 03/13] Fix Endian (#43932)
* FixEndian
* rename method
---
.../management/filters/TargetingFilter.java | 41 +++--------------
.../implementation/FeatureFilterUtils.java | 44 +++++++++++++++++++
.../filters/TargetingFilterTest.java | 4 +-
.../TargetingFilterUtilsTest.java | 20 +++++++++
4 files changed, 72 insertions(+), 37 deletions(-)
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/implemenation/TargetingFilterUtilsTest.java
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
index bea9aa4879db..7028570729d9 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
@@ -2,21 +2,17 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management.filters;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
+import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import com.azure.spring.cloud.feature.management.implementation.targeting.Audience;
import com.azure.spring.cloud.feature.management.implementation.targeting.Exclusion;
import com.azure.spring.cloud.feature.management.implementation.targeting.GroupRollout;
@@ -133,8 +129,10 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
Audience audience;
String exclusionValue = FeatureFilterUtils.getKeyCase(parameters, EXCLUSION_CAMEL);
- String exclusionUserValue = FeatureFilterUtils.getKeyCase((Map) parameters.get(exclusionValue), "Users");
- String exclusionGroupsValue = FeatureFilterUtils.getKeyCase((Map) parameters.get(exclusionValue), "Groups");
+ String exclusionUserValue = FeatureFilterUtils.getKeyCase((Map) parameters.get(exclusionValue),
+ "Users");
+ String exclusionGroupsValue = FeatureFilterUtils
+ .getKeyCase((Map) parameters.get(exclusionValue), "Groups");
if (((Map) parameters.getOrDefault(exclusionValue, new HashMap<>()))
.get(exclusionUserValue) instanceof List) {
@@ -227,35 +225,8 @@ private boolean validateTargetingContext(TargetingFilterContext targetingContext
return (!hasUserDefined && !(hasGroupsDefined && hasAtLeastOneGroup));
}
- /**
- * Computes the percentage that the contextId falls into.
- *
- * @param contextId Id of the context being targeted
- * @return the bucket value of the context id
- * @throws TargetingException Unable to create hash of target context
- */
- protected double isTargetedPercentage(String contextId) {
- byte[] hash = null;
-
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- hash = digest.digest(contextId.getBytes(Charset.defaultCharset()));
- } catch (NoSuchAlgorithmException e) {
- throw new TargetingException("Unable to find SHA-256 for targeting.", e);
- }
-
- if (hash == null) {
- throw new TargetingException("Unable to create Targeting Hash for " + contextId);
- }
-
- ByteBuffer wrapped = ByteBuffer.wrap(hash);
- int contextMarker = Math.abs(wrapped.getInt());
-
- return (contextMarker / (double) Integer.MAX_VALUE) * 100;
- }
-
private boolean isTargeted(String contextId, double percentage) {
- return isTargetedPercentage(contextId) < percentage;
+ return FeatureFilterUtils.isTargetedPercentage(contextId) < percentage;
}
/**
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
index a8631bfc9366..90deefc80b0c 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
@@ -3,11 +3,16 @@
package com.azure.spring.cloud.feature.management.implementation;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.StringUtils;
+import com.azure.spring.cloud.feature.management.models.TargetingException;
+
public class FeatureFilterUtils {
/**
@@ -34,4 +39,43 @@ public static String getKeyCase(Map parameters, String key) {
return StringUtils.uncapitalize(key);
}
+ /**
+ * Computes the percentage that the contextId falls into.
+ *
+ * @param contextId Id of the context being targeted
+ * @return the bucket value of the context id
+ * @throws TargetingException Unable to create hash of target context
+ */
+ public static double isTargetedPercentage(String contextId) {
+ byte[] hash = null;
+ if (contextId == null) {
+ contextId = "\n";
+ }
+
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ hash = digest.digest(contextId.getBytes());
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new TargetingException("Unable to find SHA-256 for targeting.", e);
+ }
+
+ if (hash == null) {
+ throw new TargetingException("Unable to create Targeting Hash for " + contextId);
+ }
+
+ BigInteger bi = bigEndianToLittleEndian(hash);
+
+ return (bi.longValue() / (Math.pow(2, 32) - 1)) * 100;
+ }
+
+ public static BigInteger bigEndianToLittleEndian(byte[] bigEndian) {
+ byte[] reversedBytes = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ reversedBytes[i] = bigEndian[3 - i];
+ }
+
+ return new BigInteger(1, reversedBytes);
+ }
+
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
index 15b4c271da7e..ed45ced91d9c 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
@@ -218,7 +218,7 @@ public void targetedGroupFiftyPass() {
TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor("Jane", targetedGroups));
- assertTrue(filter.evaluate(context));
+ assertFalse(filter.evaluate(context));
}
@Test
@@ -246,7 +246,7 @@ public void targetedGroupFiftyFalse() {
TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor("Doe", targetedGroups));
- assertFalse(filter.evaluate(context));
+ assertTrue(filter.evaluate(context));
}
@Test
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/implemenation/TargetingFilterUtilsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/implemenation/TargetingFilterUtilsTest.java
new file mode 100644
index 000000000000..ab85d8a7c543
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/implemenation/TargetingFilterUtilsTest.java
@@ -0,0 +1,20 @@
+package com.azure.spring.cloud.feature.management.implemenation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
+
+public class TargetingFilterUtilsTest {
+
+ @Test
+ public void isTargetedPercentageTest() {
+ assertEquals(FeatureFilterUtils.isTargetedPercentage(null), 9.875071074318855);
+ assertEquals(FeatureFilterUtils.isTargetedPercentage(""), 26.0813765987012);
+ assertEquals(FeatureFilterUtils.isTargetedPercentage("Alice"), 38.306839656621875);
+ assertEquals(FeatureFilterUtils.isTargetedPercentage("Quinn\nDeb"), 38.306839656621875);
+ assertEquals(FeatureFilterUtils.isTargetedPercentage("\nProd"), 79.98622464481421);
+ }
+
+}
From d3750bf731df65ddad6a5eab35d9168e885694bb Mon Sep 17 00:00:00 2001
From: Matthew Metcalf
Date: Wed, 5 Feb 2025 15:55:17 -0800
Subject: [PATCH 04/13] Feature context and Async Filters (#43435)
* Object Context and AsyncFilters
* Apply suggestions from code review
Co-authored-by: Ross Grambo
* Review comments
* Review comments
* Review Comments
---------
Co-authored-by: Ross Grambo
---
.../feature/management/FeatureManager.java | 115 +++++++++++++-----
.../filters/ContextualFeatureFilter.java | 25 ++++
.../filters/ContextualFeatureFilterAsync.java | 27 ++++
.../filters/FeatureFilterAsync.java | 25 ++++
.../management/filters/TargetingFilter.java | 65 ++++++----
.../implementation/FeatureFilterUtils.java | 9 +-
.../FeatureManagementConstants.java | 2 +
.../feature/management/models/Feature.java | 3 +
.../management/FeatureManagerTest.java | 25 ++++
.../filters/TargetingFilterTest.java | 30 ++++-
10 files changed, 263 insertions(+), 63 deletions(-)
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/FeatureFilterAsync.java
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
index 9574a65703e0..97f864d0060a 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
@@ -2,10 +2,13 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management;
+import static com.azure.spring.cloud.feature.management.implementation.FeatureManagementConstants.ALL_REQUIREMENT_TYPE;
+
+import java.time.Duration;
+import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Objects;
+import java.util.List;
import java.util.Set;
-import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -13,13 +16,18 @@
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;
+import com.azure.spring.cloud.feature.management.filters.ContextualFeatureFilter;
+import com.azure.spring.cloud.feature.management.filters.ContextualFeatureFilterAsync;
import com.azure.spring.cloud.feature.management.filters.FeatureFilter;
+import com.azure.spring.cloud.feature.management.filters.FeatureFilterAsync;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
+import com.azure.spring.cloud.feature.management.models.Conditions;
import com.azure.spring.cloud.feature.management.models.Feature;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.FilterNotFoundException;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
@@ -34,6 +42,8 @@ public class FeatureManager {
private final FeatureManagementProperties featureManagementConfigurations;
private transient FeatureManagementConfigProperties properties;
+
+ private static final Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(100);
/**
* Can be called to check if a feature is enabled or disabled.
@@ -59,7 +69,7 @@ public class FeatureManager {
* @throws FilterNotFoundException file not found
*/
public Mono isEnabledAsync(String feature) {
- return Mono.just(checkFeature(feature));
+ return checkFeature(feature, null);
}
/**
@@ -72,48 +82,93 @@ public Mono isEnabledAsync(String feature) {
* @throws FilterNotFoundException file not found
*/
public Boolean isEnabled(String feature) throws FilterNotFoundException {
- return checkFeature(feature);
+ return checkFeature(feature, null).block(DEFAULT_REQUEST_TIMEOUT);
+ }
+
+ /**
+ * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
+ * isn't found it returns false.
+ *
+ * @param feature Feature being checked.
+ * @param featureContext Local context
+ * @return state of the feature
+ * @throws FilterNotFoundException file not found
+ */
+ public Mono isEnabledAsync(String feature, Object featureContext) {
+ return checkFeature(feature, featureContext);
}
- private boolean checkFeature(String featureName) throws FilterNotFoundException {
+ /**
+ * Checks to see if the feature is enabled. If enabled it checks each filter, once a single filter returns true it
+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
+ * isn't found it returns false.
+ *
+ * @param feature Feature being checked.
+ * @param featureContext Local context
+ * @return state of the feature
+ * @throws FilterNotFoundException file not found
+ */
+ public Boolean isEnabled(String feature, Object featureContext) throws FilterNotFoundException {
+ return checkFeature(feature, featureContext).block(DEFAULT_REQUEST_TIMEOUT);
+ }
+
+ private Mono checkFeature(String featureName, Object featureContext) throws FilterNotFoundException {
Feature featureFlag = featureManagementConfigurations.getFeatureFlags().stream()
.filter(feature -> feature.getId().equals(featureName)).findAny().orElse(null);
if (featureFlag == null) {
- return false;
+ return Mono.just(false);
}
- Stream filters = featureFlag.getConditions().getClientFilters().stream()
- .filter(Objects::nonNull).filter(featureFilter -> featureFilter.getName() != null);
-
if (featureFlag.getConditions().getClientFilters().size() == 0) {
- return featureFlag.isEnabled();
- }
-
- // All Filters must be true
- if (featureFlag.getConditions().getRequirementType().equals("All")) {
- return filters.allMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
+ return Mono.just(featureFlag.isEnabled());
}
- // Any Filter must be true
- return filters.anyMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
+ return checkFeatureFilters(featureFlag, featureContext);
}
- private boolean isFeatureOn(FeatureFilterEvaluationContext filter, String feature) {
- try {
- FeatureFilter featureFilter = (FeatureFilter) context.getBean(filter.getName());
- filter.setFeatureName(feature);
-
- return featureFilter.evaluate(filter);
- } catch (NoSuchBeanDefinitionException e) {
- LOGGER.error("Was unable to find Filter {}. Does the class exist and set as an @Component?",
- filter.getName());
- if (properties.isFailFast()) {
- String message = "Fail fast is set and a Filter was unable to be found";
- ReflectionUtils.rethrowRuntimeException(new FilterNotFoundException(message, e, filter));
+ private Mono checkFeatureFilters(Feature featureFlag, Object featureContext) {
+ Conditions conditions = featureFlag.getConditions();
+ List featureFilters = conditions.getClientFilters();
+
+ if (featureFilters.size() == 0) {
+ return Mono.just(true);
+ }
+
+ List> filterResults = new ArrayList>();
+ for (FeatureFilterEvaluationContext featureFilter : featureFilters) {
+ String filterName = featureFilter.getName();
+
+ try {
+ Object filter = context.getBean(filterName);
+ featureFilter.setFeatureName(featureFlag.getId());
+ if (filter instanceof FeatureFilter) {
+ filterResults.add(Mono.just(((FeatureFilter) filter).evaluate(featureFilter)));
+ } else if (filter instanceof ContextualFeatureFilter) {
+ filterResults
+ .add(Mono.just(((ContextualFeatureFilter) filter).evaluate(featureFilter, featureContext)));
+ } else if (filter instanceof FeatureFilterAsync) {
+ filterResults.add(((FeatureFilterAsync) filter).evaluateAsync(featureFilter));
+ } else if (filter instanceof ContextualFeatureFilterAsync) {
+ filterResults
+ .add(((ContextualFeatureFilterAsync) filter).evaluateAsync(featureFilter, featureContext));
+ }
+ } catch (NoSuchBeanDefinitionException e) {
+ LOGGER.error("Was unable to find Filter {}. Does the class exist and set as an @Component?",
+ filterName);
+ if (properties.isFailFast()) {
+ String message = "Fail fast is set and a Filter was unable to be found";
+ ReflectionUtils.rethrowRuntimeException(new FilterNotFoundException(message, e, featureFilter));
+ }
}
}
- return false;
+
+ if (ALL_REQUIREMENT_TYPE.equals(featureFlag.getConditions().getRequirementType())) {
+ return Flux.merge(filterResults).reduce((a, b) -> a && b).single();
+ }
+ // Any Filter must be true
+ return Flux.merge(filterResults).reduce((a, b) -> a || b).single();
}
/**
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
new file mode 100644
index 000000000000..8432c1fbc3a9
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.feature.management.filters;
+
+import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
+
+/**
+ * A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
+ * feature management. As a Contextual feature filter any context that is passed in to the feature request will be
+ * passed along to the filter(s).
+ */
+@FunctionalInterface
+public interface ContextualFeatureFilter {
+
+ /**
+ * Evaluates if the filter is on or off. Returning true results in Feature evaluation ending and returning true.
+ * Returning false results in the next Feature evaluation to continue.
+ *
+ * @param context The context for whether or not the filter is passed.
+ * @param appContext The internal app context
+ * @return True if the feature is enabled, false otherwise.
+ */
+ boolean evaluate(FeatureFilterEvaluationContext context, Object appContext);
+
+}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
new file mode 100644
index 000000000000..6725e88ca6b7
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.feature.management.filters;
+
+import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
+ * feature management. As a Contextual feature filter any context that is passed in to the feature request will be
+ * passed along to the filter(s).
+ */
+@FunctionalInterface
+public interface ContextualFeatureFilterAsync {
+
+ /**
+ * Evaluates if the filter is on or off. Returning true results in Feature evaluation ending and returning true.
+ * Returning false results in the next Feature evaluation to continue.
+ *
+ * @param context The context for whether or not the filter is passed.
+ * @param appContext The internal app context
+ * @return true if the feature is enabled, false otherwise.
+ */
+ Mono evaluateAsync(FeatureFilterEvaluationContext context, Object appContext);
+
+}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/FeatureFilterAsync.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/FeatureFilterAsync.java
new file mode 100644
index 000000000000..e484a2795045
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/FeatureFilterAsync.java
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.feature.management.filters;
+
+import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
+ * feature management.
+ */
+@FunctionalInterface
+public interface FeatureFilterAsync {
+
+ /**
+ * Evaluates if the filter is on or off. Returning true results in Feature evaluation ending and returning true.
+ * Returning false results in the next Feature evaluation to continue.
+ *
+ * @param context The context for whether or not the filter is passed.
+ * @return True if the feature is enabled, false otherwise.
+ */
+ Mono evaluateAsync(FeatureFilterEvaluationContext context);
+
+}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
index 7028570729d9..fcaa357aa947 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
@@ -3,6 +3,7 @@
package com.azure.spring.cloud.feature.management.filters;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -14,10 +15,10 @@
import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import com.azure.spring.cloud.feature.management.implementation.targeting.Audience;
-import com.azure.spring.cloud.feature.management.implementation.targeting.Exclusion;
import com.azure.spring.cloud.feature.management.implementation.targeting.GroupRollout;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.TargetingException;
+import com.azure.spring.cloud.feature.management.targeting.TargetingContext;
import com.azure.spring.cloud.feature.management.targeting.TargetingContextAccessor;
import com.azure.spring.cloud.feature.management.targeting.TargetingEvaluationOptions;
import com.azure.spring.cloud.feature.management.targeting.TargetingFilterContext;
@@ -28,7 +29,7 @@
/**
* `Microsoft.TargetingFilter` enables evaluating a user/group/overall rollout of a feature.
*/
-public class TargetingFilter implements FeatureFilter {
+public class TargetingFilter implements FeatureFilter, ContextualFeatureFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(TargetingFilter.class);
@@ -50,10 +51,8 @@ public class TargetingFilter implements FeatureFilter {
/**
* Audience that always returns false
*/
- protected static final String EXCLUSION = "exclusion";
-
private static final String EXCLUSION_CAMEL = "Exclusion";
-
+ protected static final String EXCLUSION = "Exclusion";
/**
* Error message for when the total Audience value is greater than 100 percent.
*/
@@ -99,15 +98,27 @@ public TargetingFilter(TargetingContextAccessor contextAccessor, TargetingEvalua
}
@Override
- @SuppressWarnings("unchecked")
public boolean evaluate(FeatureFilterEvaluationContext context) {
+ return evaluate(context, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean evaluate(FeatureFilterEvaluationContext context, Object appContext) {
+
if (context == null) {
throw new IllegalArgumentException("Targeting Context not configured.");
}
- TargetingFilterContext targetingContext = new TargetingFilterContext();
+ TargetingContext targetingContext = new TargetingFilterContext();
- contextAccessor.configureTargetingContext(targetingContext);
+ if (appContext != null && appContext instanceof TargetingContext) {
+ // Use this if, there is an appContext + the contextualAccessor, or there is no contextAccessor.
+ targetingContext = (TargetingContext) appContext;
+ } else if (contextAccessor != null) {
+ // If this is the only one provided just use it.
+ contextAccessor.configureTargetingContext(targetingContext);
+ }
if (validateTargetingContext(targetingContext)) {
LOGGER.warn("No targeting context available for targeting evaluation.");
@@ -121,11 +132,8 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
parameters = (Map) audienceObject;
}
- // The correct value is Users/Groups but keeping users/groups to make sure no one is broken.
- FeatureFilterUtils.updateValueFromMapToList(parameters, USERS);
- FeatureFilterUtils.updateValueFromMapToList(parameters, USERS.toLowerCase());
- FeatureFilterUtils.updateValueFromMapToList(parameters, GROUPS);
- FeatureFilterUtils.updateValueFromMapToList(parameters, GROUPS.toLowerCase());
+ FeatureFilterUtils.updateValueFromMapToList(parameters, USERS, true);
+ FeatureFilterUtils.updateValueFromMapToList(parameters, GROUPS, true);
Audience audience;
String exclusionValue = FeatureFilterUtils.getKeyCase(parameters, EXCLUSION_CAMEL);
@@ -143,20 +151,21 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
if (exclusionMap == null) {
exclusionMap = new HashMap<>();
}
-
- audience = OBJECT_MAPPER.convertValue(parameters, Audience.class);
-
- Exclusion exclusion = new Exclusion();
+
Object users = exclusionMap.get(exclusionUserValue);
Object groups = exclusionMap.get(exclusionGroupsValue);
+
+ Map exclusion = new HashMap<>();
if (users instanceof Map) {
- exclusion.setUsers(new ArrayList<>(((Map) users).values()));
+ exclusion.put(USERS, new ArrayList<>(((Map) users).values()));
}
if (groups instanceof Map) {
- exclusion.setGroups(new ArrayList<>(((Map) groups).values()));
+ exclusion.put(GROUPS, new ArrayList<>(((Map) groups).values()));
}
- audience.setExclusion(exclusion);
+ parameters.put(exclusionValue, exclusion);
+
+ audience = OBJECT_MAPPER.convertValue(parameters, Audience.class);
}
validateSettings(audience);
@@ -194,17 +203,21 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
return isTargeted(defaultContextId, audience.getDefaultRolloutPercentage());
}
- private boolean targetUser(String userId, List users) {
+ private boolean targetUser(String userId, Collection users) {
return userId != null && users != null && users.stream().anyMatch(user -> equals(userId, user));
}
- private boolean targetGroup(Audience audience, TargetingFilterContext targetingContext,
+ private boolean targetGroup(Audience audience, TargetingContext targetingContext,
FeatureFilterEvaluationContext context, String group) {
Optional groupRollout = audience.getGroups().stream()
.filter(g -> equals(g.getName(), group)).findFirst();
if (groupRollout.isPresent()) {
- String audienceContextId = targetingContext.getUserId() + "\n" + context.getName() + "\n" + group;
+ String userId = "";
+ if (targetingContext.getUserId() != null) {
+ userId = targetingContext.getUserId();
+ }
+ String audienceContextId = userId + "\n" + context.getFeatureName() + "\n" + group;
if (isTargeted(audienceContextId, groupRollout.get().getRolloutPercentage())) {
return true;
@@ -213,7 +226,7 @@ private boolean targetGroup(Audience audience, TargetingFilterContext targetingC
return false;
}
- private boolean validateTargetingContext(TargetingFilterContext targetingContext) {
+ private boolean validateTargetingContext(TargetingContext targetingContext) {
boolean hasUserDefined = StringUtils.hasText(targetingContext.getUserId());
boolean hasGroupsDefined = targetingContext.getGroups() != null;
boolean hasAtLeastOneGroup = false;
@@ -253,8 +266,8 @@ void validateSettings(Audience audience) {
throw new TargetingException(paramName + " : " + reason);
}
- List groups = audience.getGroups();
- if (groups != null) {
+ if (audience.getGroups() != null) {
+ List groups = new ArrayList(audience.getGroups());
for (int index = 0; index < groups.size(); index++) {
GroupRollout groupRollout = groups.get(index);
if (groupRollout.getRolloutPercentage() < 0 || groupRollout.getRolloutPercentage() > 100) {
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
index 90deefc80b0c..cb25caac6203 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
@@ -15,6 +15,10 @@
public class FeatureFilterUtils {
+ public static void updateValueFromMapToList(Map parameters, String key) {
+ updateValueFromMapToList(parameters, key, false);
+ }
+
/**
* Looks at the given key in the parameters and coverts it to a list if it is currently a map.
*
@@ -22,11 +26,13 @@ public class FeatureFilterUtils {
* @param key key of object int the parameters map
*/
@SuppressWarnings("unchecked")
- public static void updateValueFromMapToList(Map parameters, String key) {
+ public static void updateValueFromMapToList(Map parameters, String key, boolean fixNull) {
Object objectMap = parameters.get(key);
if (objectMap instanceof Map) {
Collection toType = ((Map) objectMap).values();
parameters.put(key, toType);
+ } else if ((objectMap != null && objectMap.equals("")) || (objectMap == null && fixNull)) {
+ parameters.put(key, new ArrayList());
} else if (objectMap != null) {
parameters.put(key, objectMap);
}
@@ -74,7 +80,6 @@ public static BigInteger bigEndianToLittleEndian(byte[] bigEndian) {
for (int i = 0; i < 4; i++) {
reversedBytes[i] = bigEndian[3 - i];
}
-
return new BigInteger(1, reversedBytes);
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
index 1ce1fc46aca3..64165c5ddedd 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
@@ -5,5 +5,7 @@
public class FeatureManagementConstants {
public static final String DEFAULT_REQUIREMENT_TYPE = "Any";
+
+ public static final String ALL_REQUIREMENT_TYPE = "All";
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
index de00de138176..e3b43b0013c4 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
@@ -3,6 +3,8 @@
package com.azure.spring.cloud.feature.management.models;
+import org.springframework.lang.NonNull;
+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -21,6 +23,7 @@ public class Feature {
private boolean enabled;
@JsonProperty("conditions")
+ @NonNull
private Conditions conditions = new Conditions();
/**
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagerTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagerTest.java
index 75000092a2a3..e119279cfb9b 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagerTest.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/FeatureManagerTest.java
@@ -24,6 +24,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
+import com.azure.spring.cloud.feature.management.filters.ContextualFeatureFilter;
import com.azure.spring.cloud.feature.management.filters.FeatureFilter;
import com.azure.spring.cloud.feature.management.filters.TimeWindowFilter;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
@@ -88,6 +89,19 @@ public void isEnabledOnBoolean() throws InterruptedException, ExecutionException
verify(featureManagementPropertiesMock, times(2)).getFeatureFlags();
}
+ @Test
+ public void isEnabledOnContext() throws InterruptedException, ExecutionException, FilterNotFoundException {
+ List features = List.of(new Feature().setId("On").setEnabled(true).setConditions(new Conditions()
+ .setClientFilters(List.of(new FeatureFilterEvaluationContext().setName("AlwaysOnContext")))));
+
+ when(featureManagementPropertiesMock.getFeatureFlags()).thenReturn(features);
+
+ when(context.getBean(Mockito.matches("AlwaysOnContext"))).thenReturn(new AlwaysOnContextFilter());
+
+ assertFalse(featureManager.isEnabled("On", false));
+ assertFalse(featureManager.isEnabledAsync("On", false).block());
+ }
+
@Test
public void isEnabledFeatureHasNoFilters() {
List features = List.of(new Feature().setId("NoFilters").setEnabled(false)
@@ -206,6 +220,17 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
}
+ class AlwaysOnContextFilter implements ContextualFeatureFilter {
+
+ @Override
+ public boolean evaluate(FeatureFilterEvaluationContext context, Object localContext) {
+ if (localContext == Boolean.FALSE) {
+ return false;
+ }
+ return true;
+ }
+ }
+
class AlwaysOffFilter implements FeatureFilter {
@Override
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
index ed45ced91d9c..660e2b774fab 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
@@ -14,13 +14,14 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.TestConfiguration;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.TargetingException;
+import com.azure.spring.cloud.feature.management.targeting.TargetingContext;
+import com.azure.spring.cloud.feature.management.targeting.TargetingContextAccessor;
import com.azure.spring.cloud.feature.management.targeting.TargetingEvaluationOptions;
-@SpringBootTest(classes = { TestConfiguration.class, SpringBootTest.class })
+@SpringBootTest(classes = { SpringBootTest.class })
public class TargetingFilterTest {
private static final String USERS = "Users";
@@ -29,7 +30,7 @@ public class TargetingFilterTest {
private static final String AUDIENCE = "Audience";
- private static final String DEFAULT_ROLLOUT_PERCENTAGE = "defaultRolloutPercentage";
+ private static final String DEFAULT_ROLLOUT_PERCENTAGE = "DefaultRolloutPercentage";
private static final String OUT_OF_RANGE = "The value is out of the accepted range.";
@@ -59,7 +60,7 @@ public void targetedUser() {
assertTrue(filter.evaluate(context));
}
-
+
@Test
public void targetedUserLower() {
FeatureFilterEvaluationContext context = new FeatureFilterEvaluationContext();
@@ -136,7 +137,7 @@ public void targetedGroup() {
assertTrue(filter.evaluate(context));
}
-
+
@Test
public void targetedGroupLower() {
FeatureFilterEvaluationContext context = new FeatureFilterEvaluationContext();
@@ -455,4 +456,23 @@ private Map emptyExclusion() {
excludes.put(GROUPS, excludedGroups);
return excludes;
}
+
+ class TargetingFilterTestContextAccessor implements TargetingContextAccessor {
+
+ private String user;
+
+ private ArrayList groups;
+
+ TargetingFilterTestContextAccessor(String user, ArrayList groups) {
+ this.user = user;
+ this.groups = groups;
+ }
+
+ @Override
+ public void configureTargetingContext(TargetingContext context) {
+ context.setUserId(user);
+ context.setGroups(groups);
+ }
+
+ }
}
From a4de2bc5d52dbf96db1b13d8e44b34260e191aa8 Mon Sep 17 00:00:00 2001
From: Matthew Metcalf
Date: Thu, 13 Feb 2025 11:13:09 -0800
Subject: [PATCH 05/13] Push Refresh Telemetry (#44170)
* Updating to use Context to pass around telemetry + added pushRefreshEnabled
* Updated to PUSH_REFRESH + tests
---
...ationApplicationSettingPropertySource.java | 5 +-
.../AppConfigurationConstants.java | 5 +
.../AppConfigurationPropertySource.java | 3 +-
.../AppConfigurationRefreshUtil.java | 40 +++--
.../AppConfigurationReplicaClient.java | 16 +-
...ppConfigurationSnapshotPropertySource.java | 5 +-
.../AzureAppConfigDataLoader.java | 23 ++-
.../implementation/FeatureFlagClient.java | 5 +-
.../policy/BaseAppConfigurationPolicy.java | 6 +-
.../http/policy/TracingInfo.java | 7 +-
...tionSettingPropertySourceSnapshotTest.java | 17 +-
...nApplicationSettingPropertySourceTest.java | 22 ++-
...nfigurationPropertySourceKeyVaultTest.java | 16 +-
.../AppConfigurationRefreshUtilTest.java | 168 ++++++++++++++----
.../AppConfigurationReplicaClientTest.java | 62 ++++---
.../implementation/FeatureFlagClientTest.java | 24 +--
.../http/policy/TracingInfoTest.java | 24 +--
17 files changed, 306 insertions(+), 142 deletions(-)
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySource.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySource.java
index f7c2a6c90f8b..7302de70d4a2 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySource.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySource.java
@@ -18,6 +18,7 @@
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.util.StringUtils;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
@@ -60,7 +61,7 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP
* @param keyPrefixTrimValues prefixs to trim from key values
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
- public void initProperties(List keyPrefixTrimValues, boolean isRefresh) throws InvalidConfigurationPropertyValueException {
+ public void initProperties(List keyPrefixTrimValues, Context context) throws InvalidConfigurationPropertyValueException {
List labels = Arrays.asList(labelFilters);
// Reverse labels so they have the right priority order.
@@ -70,7 +71,7 @@ public void initProperties(List keyPrefixTrimValues, boolean isRefresh)
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter + "*").setLabelFilter(label);
// * for wildcard match
- processConfigurationSettings(replicaClient.listSettings(settingSelector, isRefresh), settingSelector.getKeyFilter(),
+ processConfigurationSettings(replicaClient.listSettings(settingSelector, context), settingSelector.getKeyFilter(),
keyPrefixTrimValues);
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationConstants.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationConstants.java
index 2daacc4bfd41..24e5fba7b6d0 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationConstants.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationConstants.java
@@ -36,6 +36,11 @@ public class AppConfigurationConstants {
* Constant for tracing if Key Vault is configured for use.
*/
public static final String KEY_VAULT_CONFIGURED_TRACING = "UsesKeyVault";
+
+ /**
+ * Constant for tracing if Push Refresh is enabled for the store.
+ */
+ public static final String PUSH_REFRESH = "PushRefresh";
/**
* Http Header User Agent
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySource.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySource.java
index 3a3c84907e22..9515c30258a9 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySource.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySource.java
@@ -10,6 +10,7 @@
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.core.env.EnumerablePropertySource;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.ConfigurationClient;
/**
@@ -51,5 +52,5 @@ protected static String getLabelName(String[] labelFilters) {
return String.join(",", labelFilters);
}
- protected abstract void initProperties(List trim, boolean isRefresh) throws InvalidConfigurationPropertyValueException;
+ protected abstract void initProperties(List trim, Context context) throws InvalidConfigurationPropertyValueException;
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
index 94c1b9c8550f..a5337b08e43a 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@@ -9,13 +10,16 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
import com.azure.core.exception.HttpResponseException;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlagState;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.PushNotification;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagStore;
public class AppConfigurationRefreshUtil {
@@ -50,6 +54,14 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
clientFactory.setCurrentConfigStoreClient(originEndpoint, originEndpoint);
AppConfigurationStoreMonitoring monitor = connection.getMonitoring();
+
+ boolean pushRefresh = false;
+ PushNotification notification = monitor.getPushNotification();
+ if ((notification.getPrimaryToken() != null && StringUtils.hasText(notification.getPrimaryToken().getName()))
+ || (notification.getSecondaryToken() != null && StringUtils.hasText(notification.getPrimaryToken().getName()))) {
+ pushRefresh = true;
+ }
+ Context context = new Context("refresh", true).addData(PUSH_REFRESH, pushRefresh);
List clients = clientFactory.getAvailableClients(originEndpoint);
@@ -57,7 +69,7 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
for (AppConfigurationReplicaClient client : clients) {
try {
refreshWithTime(client, StateHolder.getState(originEndpoint), monitor.getRefreshInterval(),
- eventData, replicaLookUp);
+ eventData, replicaLookUp, context);
if (eventData.getDoRefresh()) {
clientFactory.setCurrentConfigStoreClient(originEndpoint, client.getEndpoint());
return eventData;
@@ -81,7 +93,7 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
for (AppConfigurationReplicaClient client : clients) {
try {
refreshWithTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(originEndpoint),
- monitor.getFeatureFlagRefreshInterval(), eventData, replicaLookUp);
+ monitor.getFeatureFlagRefreshInterval(), eventData, replicaLookUp, context);
if (eventData.getDoRefresh()) {
clientFactory.setCurrentConfigStoreClient(originEndpoint, client.getEndpoint());
return eventData;
@@ -115,10 +127,10 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
* @param originEndpoint config store origin endpoint
* @return A refresh should be triggered.
*/
- static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String originEndpoint) {
+ static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String originEndpoint, Context context) {
RefreshEventData eventData = new RefreshEventData();
if (StateHolder.getLoadState(originEndpoint)) {
- refreshWithoutTime(client, StateHolder.getState(originEndpoint).getWatchKeys(), eventData);
+ refreshWithoutTime(client, StateHolder.getState(originEndpoint).getWatchKeys(), eventData, context);
}
return eventData.getDoRefresh();
}
@@ -131,12 +143,12 @@ static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String or
* @return true if a refresh should be triggered.
*/
static boolean refreshStoreFeatureFlagCheck(Boolean featureStoreEnabled,
- AppConfigurationReplicaClient client) {
+ AppConfigurationReplicaClient client, Context context) {
RefreshEventData eventData = new RefreshEventData();
String endpoint = client.getEndpoint();
if (featureStoreEnabled && StateHolder.getStateFeatureFlag(endpoint) != null) {
- refreshWithoutTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(endpoint), eventData);
+ refreshWithoutTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(endpoint), eventData, context);
} else {
LOGGER.debug("Skipping feature flag refresh check for " + endpoint);
}
@@ -151,10 +163,10 @@ static boolean refreshStoreFeatureFlagCheck(Boolean featureStoreEnabled,
* @param eventData Info for this refresh event.
*/
private static void refreshWithTime(AppConfigurationReplicaClient client, State state, Duration refreshInterval,
- RefreshEventData eventData, ReplicaLookUp replicaLookUp) throws AppConfigurationStatusException {
+ RefreshEventData eventData, ReplicaLookUp replicaLookUp, Context context) throws AppConfigurationStatusException {
if (Instant.now().isAfter(state.getNextRefreshCheck())) {
replicaLookUp.updateAutoFailoverEndpoints();
- refreshWithoutTime(client, state.getWatchKeys(), eventData);
+ refreshWithoutTime(client, state.getWatchKeys(), eventData, context);
StateHolder.getCurrentState().updateStateRefresh(state, refreshInterval);
}
@@ -168,9 +180,9 @@ private static void refreshWithTime(AppConfigurationReplicaClient client, State
* @param eventData Refresh event info
*/
private static void refreshWithoutTime(AppConfigurationReplicaClient client, List watchKeys,
- RefreshEventData eventData) throws AppConfigurationStatusException {
+ RefreshEventData eventData, Context context) throws AppConfigurationStatusException {
for (ConfigurationSetting watchKey : watchKeys) {
- ConfigurationSetting watchedKey = client.getWatchKey(watchKey.getKey(), watchKey.getLabel(), true);
+ ConfigurationSetting watchedKey = client.getWatchKey(watchKey.getKey(), watchKey.getLabel(), context);
// If there is no result, etag will be considered empty.
// A refresh will trigger once the selector returns a value.
@@ -184,14 +196,14 @@ private static void refreshWithoutTime(AppConfigurationReplicaClient client, Lis
}
private static void refreshWithTimeFeatureFlags(AppConfigurationReplicaClient client, FeatureFlagState state,
- Duration refreshInterval, RefreshEventData eventData, ReplicaLookUp replicaLookUp)
+ Duration refreshInterval, RefreshEventData eventData, ReplicaLookUp replicaLookUp, Context context)
throws AppConfigurationStatusException {
Instant date = Instant.now();
if (date.isAfter(state.getNextRefreshCheck())) {
replicaLookUp.updateAutoFailoverEndpoints();
for (FeatureFlags featureFlags : state.getWatchKeys()) {
- if (client.checkWatchKeys(featureFlags.getSettingSelector(), true)) {
+ if (client.checkWatchKeys(featureFlags.getSettingSelector(), context)) {
String eventDataInfo = ".appconfig.featureflag/*";
// Only one refresh Event needs to be call to update all of the
@@ -209,10 +221,10 @@ private static void refreshWithTimeFeatureFlags(AppConfigurationReplicaClient cl
}
private static void refreshWithoutTimeFeatureFlags(AppConfigurationReplicaClient client, FeatureFlagState watchKeys,
- RefreshEventData eventData) throws AppConfigurationStatusException {
+ RefreshEventData eventData, Context context) throws AppConfigurationStatusException {
for (FeatureFlags featureFlags : watchKeys.getWatchKeys()) {
- if (client.checkWatchKeys(featureFlags.getSettingSelector(), true)) {
+ if (client.checkWatchKeys(featureFlags.getSettingSelector(), context)) {
String eventDataInfo = ".appconfig.featureflag/*";
// Only one refresh Event needs to be call to update all of the
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClient.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClient.java
index 5eea371940c2..95e3e90768b1 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClient.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClient.java
@@ -88,10 +88,9 @@ String getEndpoint() {
* @param label String value of the watch key, use \0 for null.
* @return The first returned configuration.
*/
- ConfigurationSetting getWatchKey(String key, String label, boolean isRefresh)
+ ConfigurationSetting getWatchKey(String key, String label, Context context)
throws HttpResponseException {
try {
- Context context = new Context("refresh", isRefresh);
ConfigurationSetting selector = new ConfigurationSetting().setKey(key).setLabel(label);
ConfigurationSetting watchKey = NormalizeNull
.normalizeNullLabel(
@@ -111,11 +110,10 @@ ConfigurationSetting getWatchKey(String key, String label, boolean isRefresh)
* @param settingSelector Information on which setting to pull. i.e. number of results, key value...
* @return List of Configuration Settings.
*/
- List listSettings(SettingSelector settingSelector, boolean isRefresh)
+ List listSettings(SettingSelector settingSelector, Context context)
throws HttpResponseException {
List configurationSettings = new ArrayList<>();
try {
- Context context = new Context("refresh", isRefresh);
PagedIterable settings = client.listConfigurationSettings(settingSelector, context);
settings.forEach(setting -> {
configurationSettings.add(NormalizeNull.normalizeNullLabel(setting));
@@ -130,11 +128,11 @@ List listSettings(SettingSelector settingSelector, boolean
}
}
- FeatureFlags listFeatureFlags(SettingSelector settingSelector, boolean isRefresh) throws HttpResponseException {
+ FeatureFlags listFeatureFlags(SettingSelector settingSelector, Context context)
+ throws HttpResponseException {
List configurationSettings = new ArrayList<>();
List checks = new ArrayList<>();
try {
- Context context = new Context("refresh", isRefresh);
client.listConfigurationSettings(settingSelector, context).streamByPage().forEach(pagedResponse -> {
checks.add(
new MatchConditions().setIfNoneMatch(pagedResponse.getHeaders().getValue(HttpHeaderName.ETAG)));
@@ -155,12 +153,11 @@ FeatureFlags listFeatureFlags(SettingSelector settingSelector, boolean isRefresh
}
}
- List listSettingSnapshot(String snapshotName, boolean isRefresh) {
+ List listSettingSnapshot(String snapshotName, Context context) {
List configurationSettings = new ArrayList<>();
try {
// Because Spring always refreshes all we still have to load snapshots on refresh to build the property
// sources.
- Context context = new Context("refresh", isRefresh);
ConfigurationSnapshot snapshot = client.getSnapshotWithResponse(snapshotName, null, context).getValue();
if (!SnapshotComposition.KEY.equals(snapshot.getSnapshotComposition())) {
throw new IllegalArgumentException("Snapshot " + snapshotName + " needs to be of type Key.");
@@ -177,8 +174,7 @@ List listSettingSnapshot(String snapshotName, boolean isRe
}
}
- boolean checkWatchKeys(SettingSelector settingSelector, boolean isRefresh) {
- Context context = new Context("refresh", false);
+ boolean checkWatchKeys(SettingSelector settingSelector, Context context) {
List> results = client.listConfigurationSettings(settingSelector, context)
.streamByPage().filter(pagedResponse -> pagedResponse.getStatusCode() != 304).toList();
return results.size() > 0;
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationSnapshotPropertySource.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationSnapshotPropertySource.java
index 853db7e8ab11..e585997e05a5 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationSnapshotPropertySource.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationSnapshotPropertySource.java
@@ -7,6 +7,7 @@
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
@@ -45,8 +46,8 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli
* @param isRefresh true if a refresh triggered the loading of the Snapshot.
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
- public void initProperties(List trim, boolean isRefresh) throws InvalidConfigurationPropertyValueException {
- processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, isRefresh), null, trim);
+ public void initProperties(List trim, Context context) throws InvalidConfigurationPropertyValueException {
+ processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, context), null, trim);
FeatureFlags featureFlags = new FeatureFlags(null, featureFlagsList);
featureFlagClient.proccessFeatureFlags(featureFlags, replicaClient.getEndpoint());
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java
index 72c1e03c3a3f..82bbd918900c 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigDataLoader.java
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
@@ -19,11 +20,13 @@
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.StringUtils;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProviderProperties;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.PushNotification;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagKeyValueSelector;
@EnableConfigurationProperties(AppConfigurationProviderProperties.class)
@@ -40,6 +43,8 @@ public class AzureAppConfigDataLoader implements ConfigDataLoader watchKeysSettings = monitoring.getTriggers().stream()
.map(trigger -> client.getWatchKey(trigger.getKey(), trigger.getLabel(),
- resource.isRefresh()))
+ requestContext))
.toList();
storeState.setState(resource.getEndpoint(), watchKeysSettings, monitoring.getRefreshInterval());
@@ -157,7 +171,7 @@ private List createSettings(AppConfigurationRepl
selectedKeys.getKeyFilter() + resource.getEndpoint() + "/", client, keyVaultClientFactory,
selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles));
}
- propertySource.initProperties(resource.getTrimKeyPrefix(), resource.isRefresh());
+ propertySource.initProperties(resource.getTrimKeyPrefix(), requestContext);
sourceList.add(propertySource);
}
return sourceList;
@@ -174,9 +188,10 @@ private List createFeatureFlags(AppConfigurationReplicaClient clie
throws Exception {
List featureFlagWatchKeys = new ArrayList<>();
List profiles = resource.getProfiles().getActive();
+
for (FeatureFlagKeyValueSelector selectedKeys : resource.getFeatureFlagSelects()) {
List storesFeatureFlags = featureFlagClient.loadFeatureFlags(client,
- selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles), resource.isRefresh());
+ selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles), requestContext);
featureFlagWatchKeys.addAll(storesFeatureFlags);
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
index 474f0fda49f0..047dee654f29 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClient.java
@@ -26,6 +26,7 @@
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
@@ -64,7 +65,7 @@ class FeatureFlagClient {
*
*/
List loadFeatureFlags(AppConfigurationReplicaClient replicaClient, String customKeyFilter,
- String[] labelFilter, boolean isRefresh) {
+ String[] labelFilter, Context context) {
List loadedFeatureFlags = new ArrayList<>();
String keyFilter = SELECT_ALL_FEATURE_FLAGS;
@@ -79,7 +80,7 @@ List loadFeatureFlags(AppConfigurationReplicaClient replicaClient,
for (String label : labels) {
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter).setLabelFilter(label);
- FeatureFlags features = replicaClient.listFeatureFlags(settingSelector, isRefresh);
+ FeatureFlags features = replicaClient.listFeatureFlags(settingSelector, context);
loadedFeatureFlags.addAll(proccessFeatureFlags(features, keyFilter));
}
return loadedFeatureFlags;
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
index bd72467aa3f9..e46a413fac23 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/BaseAppConfigurationPolicy.java
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation.http.policy;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
+
import org.springframework.util.StringUtils;
import com.azure.core.http.HttpHeaderName;
@@ -40,15 +42,15 @@ public BaseAppConfigurationPolicy(TracingInfo tracingInfo) {
this.tracingInfo = tracingInfo;
}
-
@Override
public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
Boolean watchRequests = (Boolean) context.getData("refresh").orElse(false);
+ Boolean pushRefresh = (Boolean) context.getData(PUSH_REFRESH).orElse(false);
HttpHeaders headers = context.getHttpRequest().getHeaders();
String sdkUserAgent = headers.get(HttpHeaderName.USER_AGENT).getValue();
headers.set(HttpHeaderName.USER_AGENT, USER_AGENT + " " + sdkUserAgent);
headers.set(HttpHeaderName.fromString(AppConfigurationConstants.CORRELATION_CONTEXT),
- tracingInfo.getValue(watchRequests));
+ tracingInfo.getValue(watchRequests, pushRefresh));
return next.process();
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
index 5f57b8e9ab78..22f1b74118ff 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfo.java
@@ -3,6 +3,7 @@
package com.azure.spring.cloud.appconfiguration.config.implementation.http.policy;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.KEY_VAULT_CONFIGURED_TRACING;
+import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
import org.springframework.util.StringUtils;
@@ -28,7 +29,7 @@ public TracingInfo(boolean isKeyVaultConfigured, int replicaCount, Configuration
this.configuration = configuration;
}
- String getValue(boolean watchRequests) {
+ String getValue(boolean watchRequests, boolean pushRefresh) {
String track = configuration.get(RequestTracingConstants.REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE.toString());
if (track != null && Boolean.valueOf(track)) {
return "";
@@ -50,6 +51,10 @@ String getValue(boolean watchRequests) {
if (isKeyVaultConfigured) {
sb.append(",").append(KEY_VAULT_CONFIGURED_TRACING);
}
+
+ if (pushRefresh) {
+ sb.append(",").append(PUSH_REFRESH);
+ }
if (replicaCount > 0) {
sb.append(",").append(RequestTracingConstants.REPLICA_COUNT).append("=").append(replicaCount);
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
index 7ef8af30b518..c4c9c95c025a 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceSnapshotTest.java
@@ -39,12 +39,14 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+
public class AppConfigurationApplicationSettingPropertySourceSnapshotTest {
private static final String EMPTY_CONTENT_TYPE = "";
@@ -90,6 +92,9 @@ public class AppConfigurationApplicationSettingPropertySourceSnapshotTest {
@Mock
private List configurationListMock;
+ @Mock
+ private Context contextMock;
+
FeatureFlagClient featureFlagLoader = new FeatureFlagClient();
private MockitoSession session;
@@ -128,10 +133,10 @@ public void cleanup() throws Exception {
@Test
public void testPropCanBeInitAndQueried() throws IOException {
when(configurationListMock.iterator()).thenReturn(testItems.iterator()).thenReturn(testItems.iterator());
- when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(TRIM, false);
+ propertySource.initProperties(TRIM, contextMock);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames = testItems.stream().map(t -> {
@@ -155,10 +160,10 @@ public void testPropertyNameSlashConvertedToDots() throws IOException {
List settings = new ArrayList<>();
settings.add(slashedProp);
when(configurationListMock.iterator()).thenReturn(settings.iterator()).thenReturn(Collections.emptyIterator());
- when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(TRIM, false);
+ propertySource.initProperties(TRIM, contextMock);
String expectedKeyName = TEST_SLASH_KEY.replace('/', '.');
String[] actualKeyNames = propertySource.getPropertyNames();
@@ -174,9 +179,9 @@ public void initNullValidContentTypeTest() throws IOException {
List items = new ArrayList<>();
items.add(ITEM_NULL);
when(configurationListMock.iterator()).thenReturn(items.iterator()).thenReturn(Collections.emptyIterator());
- when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock);
+ when(clientMock.listSettingSnapshot(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock);
- propertySource.initProperties(TRIM, false);
+ propertySource.initProperties(TRIM, contextMock);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames =
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceTest.java
index 1f7fe5ce9760..6aac4f284cb8 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySourceTest.java
@@ -34,12 +34,13 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
public class AppConfigurationApplicationSettingPropertySourceTest {
@@ -82,6 +83,9 @@ public class AppConfigurationApplicationSettingPropertySourceTest {
@Mock
private List configurationListMock;
+ @Mock
+ private Context contextMock;
+
private MockitoSession session;
@BeforeAll
@@ -116,10 +120,10 @@ public void cleanup() throws Exception {
@Test
public void testPropCanBeInitAndQueried() throws IOException {
when(configurationListMock.iterator()).thenReturn(testItems.iterator());
- when(clientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
+ when(clientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(null, false);
+ propertySource.initProperties(null, contextMock);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames = testItems.stream()
@@ -140,10 +144,10 @@ public void testPropertyNameSlashConvertedToDots() throws IOException {
settings.add(slashedProp);
when(configurationListMock.iterator()).thenReturn(settings.iterator())
.thenReturn(Collections.emptyIterator());
- when(clientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock)
+ when(clientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock)
.thenReturn(configurationListMock);
- propertySource.initProperties(null, false);
+ propertySource.initProperties(null, contextMock);
String expectedKeyName = TEST_SLASH_KEY.replace('/', '.');
String[] actualKeyNames = propertySource.getPropertyNames();
@@ -160,9 +164,9 @@ public void initNullValidContentTypeTest() throws IOException {
items.add(ITEM_NULL);
when(configurationListMock.iterator()).thenReturn(items.iterator())
.thenReturn(Collections.emptyIterator());
- when(clientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock);
+ when(clientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock);
- propertySource.initProperties(null, false);
+ propertySource.initProperties(null, contextMock);
String[] keyNames = propertySource.getPropertyNames();
String[] expectedKeyNames = items.stream()
@@ -177,9 +181,9 @@ public void jsonContentTypeWithInvalidJsonValueTest() {
items.add(ITEM_INVALID_JSON);
when(configurationListMock.iterator()).thenReturn(items.iterator())
.thenReturn(Collections.emptyIterator());
- when(clientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(configurationListMock);
+ when(clientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(configurationListMock);
- assertThatThrownBy(() -> propertySource.initProperties(null, false))
+ assertThatThrownBy(() -> propertySource.initProperties(null, contextMock))
.isInstanceOf(InvalidConfigurationPropertyValueException.class)
.hasMessageNotContaining(ITEM_INVALID_JSON.getValue());
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
index 0bc44e5c546c..74d97b275702 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySourceKeyVaultTest.java
@@ -30,6 +30,7 @@
import org.mockito.quality.Strictness;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
import com.azure.security.keyvault.secrets.SecretAsyncClient;
@@ -70,6 +71,9 @@ public class AppConfigurationPropertySourceKeyVaultTest {
@Mock
private List keyVaultSecretListMock;
+
+ @Mock
+ private Context contextMock;
private MockitoSession session;
@@ -98,7 +102,7 @@ public void testKeyVaultTest() {
List settings = List.of(KEY_VAULT_ITEM);
when(keyVaultSecretListMock.iterator()).thenReturn(settings.iterator())
.thenReturn(Collections.emptyIterator());
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(keyVaultSecretListMock)
+ when(replicaClientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(keyVaultSecretListMock)
.thenReturn(keyVaultSecretListMock);
KeyVaultSecret secret = new KeyVaultSecret("mySecret", "mySecretValue");
@@ -107,7 +111,7 @@ public void testKeyVaultTest() {
when(clientManagerMock.getSecret(Mockito.any(URI.class))).thenReturn(secret);
try {
- propertySource.initProperties(null, false);
+ propertySource.initProperties(null, contextMock);
} catch (InvalidConfigurationPropertyValueException e) {
fail("Failed Reading in Feature Flags");
}
@@ -125,11 +129,11 @@ public void invalidKeyVaultReferenceInvalidURITest() {
List settings = List.of(KEY_VAULT_ITEM_INVALID_URI);
when(keyVaultSecretListMock.iterator()).thenReturn(settings.iterator())
.thenReturn(Collections.emptyIterator());
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(keyVaultSecretListMock)
+ when(replicaClientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(keyVaultSecretListMock)
.thenReturn(keyVaultSecretListMock);
InvalidConfigurationPropertyValueException exception = assertThrows(
- InvalidConfigurationPropertyValueException.class, () -> propertySource.initProperties(null, false));
+ InvalidConfigurationPropertyValueException.class, () -> propertySource.initProperties(null, contextMock));
assertEquals("test_key_vault_1", exception.getName());
assertEquals("", exception.getValue());
assertEquals("Invalid URI found in JSON property field 'uri' unable to parse.", exception.getReason());
@@ -140,13 +144,13 @@ public void invalidKeyVaultReferenceParseErrorTest() {
List settings = List.of(KEY_VAULT_ITEM);
when(keyVaultSecretListMock.iterator()).thenReturn(settings.iterator())
.thenReturn(Collections.emptyIterator());
- when(replicaClientMock.listSettings(Mockito.any(), Mockito.anyBoolean())).thenReturn(keyVaultSecretListMock)
+ when(replicaClientMock.listSettings(Mockito.any(), Mockito.any(Context.class))).thenReturn(keyVaultSecretListMock)
.thenReturn(keyVaultSecretListMock);
when(keyVaultClientFactoryMock.getClient(Mockito.eq("https://test.key.vault.com")))
.thenReturn(clientManagerMock);
when(clientManagerMock.getSecret(Mockito.any())).thenThrow(new RuntimeException("Parse Failed"));
- RuntimeException exception = assertThrows(RuntimeException.class, () -> propertySource.initProperties(null, false));
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> propertySource.initProperties(null, contextMock));
assertEquals("Parse Failed", exception.getMessage());
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
index 161681f4740b..9e5ab6bcaf9e 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
@@ -21,6 +21,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
@@ -28,6 +29,7 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
@@ -36,6 +38,8 @@
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlagState;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.AccessToken;
+import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.PushNotification;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.ConfigStore;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagKeyValueSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagStore;
@@ -62,6 +66,9 @@ public class AppConfigurationRefreshUtilTest {
@Mock
private ReplicaLookUp replicaLookUpMock;
+ @Mock
+ private Context contextMock;
+
private ConfigStore configStore;
private String endpoint;
@@ -105,8 +112,8 @@ public void refreshWithoutTimeWatchKeyConfigStoreNotLoaded(TestInfo testInfo) {
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(false);
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
}
}
@@ -119,13 +126,14 @@ public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNotReturned(TestInfo te
State newState = new State(watchKeys, Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
// Config Store doesn't return a watch key change.
- when(clientMock.getWatchKey(Mockito.eq(KEY_FILTER), Mockito.eq(EMPTY_LABEL), Mockito.anyBoolean())).thenReturn(watchKeys.get(0));
+ when(clientMock.getWatchKey(Mockito.eq(KEY_FILTER), Mockito.eq(EMPTY_LABEL), Mockito.any(Context.class)))
+ .thenReturn(watchKeys.get(0));
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(true);
stateHolderMock.when(() -> StateHolder.getState(endpoint)).thenReturn(newState);
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
}
}
@@ -139,12 +147,13 @@ public void refreshWithoutTimeWatchKeyConfigStoreWatchKeyNoChange(TestInfo testI
Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
// Config Store does return a watch key change.
- when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.anyBoolean())).thenReturn(false);
+ when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.any(Context.class)))
+ .thenReturn(false);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
}
}
@@ -156,8 +165,8 @@ public void refreshWithoutTimeFeatureFlagDisabled(TestInfo testInfo) {
configStore.getFeatureFlags().setEnabled(false);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
stateHolderMock.verify(() -> StateHolder.getLoadState(Mockito.anyString()), times(1));
}
}
@@ -170,8 +179,8 @@ public void refreshWithoutTimeFeatureFlagNotLoaded(TestInfo testInfo) {
configStore.getFeatureFlags().setEnabled(true);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
stateHolderMock.verify(() -> StateHolder.getLoadState(Mockito.anyString()), times(1));
}
}
@@ -180,18 +189,19 @@ public void refreshWithoutTimeFeatureFlagNotLoaded(TestInfo testInfo) {
public void refreshWithoutTimeFeatureFlagNoChange(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
-
+
FeatureFlagState newState = new FeatureFlagState(
List.of(new FeatureFlags(new SettingSelector().setKeyFilter(KEY_FILTER).setLabelFilter(EMPTY_LABEL), null)),
Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
// Config Store doesn't return a watch key change.
- when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.anyBoolean())).thenReturn(false);
+ when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.any(Context.class)))
+ .thenReturn(false);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
}
}
@@ -200,18 +210,19 @@ public void refreshWithoutTimeFeatureFlagNoChange(TestInfo testInfo) {
public void refreshWithoutTimeFeatureFlagEtagChanged(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
when(clientMock.getEndpoint()).thenReturn(endpoint);
-
+
FeatureFlags featureFlags = new FeatureFlags(new SettingSelector(), watchKeysFeatureFlags);
FeatureFlagState newState = new FeatureFlagState(List.of(featureFlags),
Math.toIntExact(Duration.ofMinutes(10).getSeconds()), endpoint);
// Config Store does return a watch key change.
- when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.anyBoolean())).thenReturn(true);
+ when(clientMock.checkWatchKeys(Mockito.any(SettingSelector.class), Mockito.any(Context.class)))
+ .thenReturn(true);
try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
stateHolderMock.when(() -> StateHolder.getStateFeatureFlag(endpoint)).thenReturn(newState);
- assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint));
- assertTrue(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock));
+ assertFalse(AppConfigurationRefreshUtil.refreshStoreCheck(clientMock, endpoint, contextMock));
+ assertTrue(AppConfigurationRefreshUtil.refreshStoreFeatureFlagCheck(true, clientMock, contextMock));
}
}
@@ -233,7 +244,8 @@ public void refreshStoresCheckSettingsTestNotEnabled(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
@@ -252,7 +264,8 @@ public void refreshStoresCheckSettingsTestNotLoaded(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
@@ -272,7 +285,8 @@ public void refreshStoresCheckSettingsTestNotRefreshTime(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
@@ -293,9 +307,14 @@ public void refreshStoresCheckSettingsTestFailedRequest(TestInfo testInfo) {
Duration.ofMinutes(10),
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
+ ArgumentCaptor captorParam = ArgumentCaptor.forClass(Context.class);
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ captorParam.capture());
assertEquals(newState, StateHolder.getState(endpoint));
+ Context testContext = captorParam.getValue();
+ assertTrue((Boolean) testContext.getData("refresh").get());
+ assertFalse((Boolean) testContext.getData("PushRefresh").get());
}
}
@@ -304,7 +323,77 @@ public void refreshStoresCheckSettingsTestRefreshTimeNoChange(TestInfo testInfo)
endpoint = testInfo.getDisplayName() + ".azconfig.io";
setupFeatureFlagLoad();
- when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean()))
+ when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.any(Context.class)))
+ .thenReturn(generateWatchKeys().get(0));
+
+ State newState = new State(generateWatchKeys(), Math.toIntExact(Duration.ofMinutes(-1).getSeconds()), endpoint);
+
+ try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
+ stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(true);
+ stateHolderMock.when(() -> StateHolder.getState(Mockito.any())).thenReturn(newState);
+ StateHolder updatedStateHolder = new StateHolder();
+ stateHolderMock.when(() -> StateHolder.getCurrentState()).thenReturn(updatedStateHolder);
+
+ RefreshEventData eventData = new AppConfigurationRefreshUtil().refreshStoresCheck(clientFactoryMock,
+ Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
+ assertEquals(newState, StateHolder.getState(endpoint));
+ assertFalse(eventData.getDoRefresh());
+ verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
+ }
+ }
+
+ @Test
+ public void refreshStoresPushRefreshEnabledPrimary(TestInfo testInfo) {
+ endpoint = testInfo.getDisplayName() + ".azconfig.io";
+ setupFeatureFlagLoad();
+ PushNotification pushNotificaiton = new PushNotification();
+ AccessToken p1 = new AccessToken();
+ p1.setName("fake name");
+ p1.setSecret("value");
+ pushNotificaiton.setPrimaryToken(p1);
+ monitoring.setPushNotification(pushNotificaiton);
+ when(connectionManagerMock.getMonitoring()).thenReturn(monitoring);
+
+ when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.any(Context.class)))
+ .thenReturn(generateWatchKeys().get(0));
+
+ State newState = new State(generateWatchKeys(), Math.toIntExact(Duration.ofMinutes(-1).getSeconds()), endpoint);
+
+ try (MockedStatic stateHolderMock = Mockito.mockStatic(StateHolder.class)) {
+ stateHolderMock.when(() -> StateHolder.getLoadState(endpoint)).thenReturn(true);
+ stateHolderMock.when(() -> StateHolder.getState(Mockito.any())).thenReturn(newState);
+ StateHolder updatedStateHolder = new StateHolder();
+ stateHolderMock.when(() -> StateHolder.getCurrentState()).thenReturn(updatedStateHolder);
+
+ RefreshEventData eventData = new AppConfigurationRefreshUtil().refreshStoresCheck(clientFactoryMock,
+ Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
+ assertEquals(newState, StateHolder.getState(endpoint));
+ assertFalse(eventData.getDoRefresh());
+ ArgumentCaptor captorParam = ArgumentCaptor.forClass(Context.class);
+ verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ captorParam.capture());
+ Context testContext = captorParam.getValue();
+ assertTrue((Boolean) testContext.getData("refresh").get());
+ assertTrue((Boolean) testContext.getData("PushRefresh").get());
+ }
+ }
+
+ @Test
+ public void refreshStoresPushRefreshEnabledSecondary(TestInfo testInfo) {
+ endpoint = testInfo.getDisplayName() + ".azconfig.io";
+ setupFeatureFlagLoad();
+ PushNotification pushNotificaiton = new PushNotification();
+ AccessToken p2 = new AccessToken();
+ p2.setName("fake name");
+ p2.setSecret("value");
+ pushNotificaiton.setPrimaryToken(p2);
+ monitoring.setPushNotification(pushNotificaiton);
+ when(connectionManagerMock.getMonitoring()).thenReturn(monitoring);
+
+ when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.any(Context.class)))
.thenReturn(generateWatchKeys().get(0));
State newState = new State(generateWatchKeys(), Math.toIntExact(Duration.ofMinutes(-1).getSeconds()), endpoint);
@@ -319,8 +408,13 @@ public void refreshStoresCheckSettingsTestRefreshTimeNoChange(TestInfo testInfo)
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertEquals(newState, StateHolder.getState(endpoint));
assertFalse(eventData.getDoRefresh());
+ ArgumentCaptor captorParam = ArgumentCaptor.forClass(Context.class);
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ captorParam.capture());
+ Context testContext = captorParam.getValue();
+ assertTrue((Boolean) testContext.getData("refresh").get());
+ assertTrue((Boolean) testContext.getData("PushRefresh").get());
}
}
@@ -336,7 +430,8 @@ public void refreshStoresCheckSettingsTestTriggerRefresh(TestInfo testInfo) {
ConfigurationSetting refreshKey = new ConfigurationSetting().setKey(KEY_FILTER).setLabel(EMPTY_LABEL)
.setETag("new");
- when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean())).thenReturn(refreshKey);
+ when(clientOriginMock.getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.any(Context.class)))
+ .thenReturn(refreshKey);
State newState = new State(generateWatchKeys(), Math.toIntExact(Duration.ofMinutes(-1).getSeconds()), endpoint);
@@ -350,7 +445,8 @@ public void refreshStoresCheckSettingsTestTriggerRefresh(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertTrue(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(1)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
verify(currentStateMock, times(1)).updateStateRefresh(Mockito.any(), Mockito.any());
}
}
@@ -373,7 +469,8 @@ public void refreshStoresCheckFeatureFlagTestNotLoaded(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
@@ -395,7 +492,8 @@ public void refreshStoresCheckFeatureFlagTestNotRefreshTime(TestInfo testInfo) {
(long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
@@ -407,7 +505,7 @@ public void refreshStoresCheckFeatureFlagTestNoChange(TestInfo testInfo) {
configStore.setMonitoring(monitoring);
setupFeatureFlagLoad();
- when(clientOriginMock.checkWatchKeys(Mockito.any(), Mockito.anyBoolean())).thenReturn(false);
+ when(clientOriginMock.checkWatchKeys(Mockito.any(), Mockito.any(Context.class))).thenReturn(false);
FeatureFlagState newState = new FeatureFlagState(
List.of(new FeatureFlags(new SettingSelector().setKeyFilter(KEY_FILTER).setLabelFilter(EMPTY_LABEL), null)),
@@ -423,7 +521,8 @@ public void refreshStoresCheckFeatureFlagTestNoChange(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertFalse(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
verify(currentStateMock, times(1)).updateFeatureFlagStateRefresh(Mockito.any(), Mockito.any());
}
@@ -433,7 +532,7 @@ public void refreshStoresCheckFeatureFlagTestNoChange(TestInfo testInfo) {
public void refreshStoresCheckFeatureFlagTestTriggerRefresh(TestInfo testInfo) {
endpoint = testInfo.getDisplayName() + ".azconfig.io";
setupFeatureFlagLoad();
- when(clientOriginMock.checkWatchKeys(Mockito.any(), Mockito.anyBoolean())).thenReturn(true);
+ when(clientOriginMock.checkWatchKeys(Mockito.any(), Mockito.any(Context.class))).thenReturn(true);
FeatureFlags featureFlags = new FeatureFlags(new SettingSelector(), watchKeysFeatureFlags);
@@ -450,7 +549,8 @@ public void refreshStoresCheckFeatureFlagTestTriggerRefresh(TestInfo testInfo) {
Duration.ofMinutes(10), (long) 60, replicaLookUpMock);
assertTrue(eventData.getDoRefresh());
verify(clientFactoryMock, times(1)).setCurrentConfigStoreClient(Mockito.eq(endpoint), Mockito.eq(endpoint));
- verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
+ verify(clientOriginMock, times(0)).getWatchKey(Mockito.anyString(), Mockito.anyString(),
+ Mockito.any(Context.class));
}
}
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
index 9e328cffd751..f19f3c54532f 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClientTest.java
@@ -37,6 +37,7 @@
import com.azure.core.http.rest.PagedResponse;
import com.azure.core.http.rest.PagedResponseBase;
import com.azure.core.http.rest.Response;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.ConfigurationClient;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.ConfigurationSnapshot;
@@ -69,6 +70,9 @@ public class AppConfigurationReplicaClientTest {
@Mock
private Response snapshotResponseMock;
+
+ @Mock
+ private Context contextMock;
private final String endpoint = "clientTest.azconfig.io";
@@ -96,25 +100,25 @@ public void getWatchKeyTest() {
Mockito.any())).thenReturn(configurationSettingResponse);
when(configurationSettingResponse.getValue()).thenReturn(watchKey);
- assertEquals(watchKey, client.getWatchKey("watch", "\0", false));
+ assertEquals(watchKey, client.getWatchKey("watch", "\0", contextMock));
when(configurationSettingResponse.getValue()).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
- assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", contextMock));
when(responseMock.getStatusCode()).thenReturn(408);
- assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", contextMock));
when(responseMock.getStatusCode()).thenReturn(500);
- assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", contextMock));
when(responseMock.getStatusCode()).thenReturn(499);
- assertThrows(HttpResponseException.class, () -> client.getWatchKey("watch", "\0", false));
+ assertThrows(HttpResponseException.class, () -> client.getWatchKey("watch", "\0", contextMock));
when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.any(), Mockito.anyBoolean(),
Mockito.any())).thenThrow(new UncheckedIOException(new UnknownHostException()));
- assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.getWatchKey("watch", "\0", contextMock));
}
@Test
@@ -132,21 +136,21 @@ public void listSettingsTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenReturn(new PagedIterable<>(pagedFlux));
- assertEquals(configurations, client.listSettings(new SettingSelector(), false));
+ assertEquals(configurations, client.listSettings(new SettingSelector(), contextMock));
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any())).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(408);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(500);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(499);
- assertThrows(HttpResponseException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(HttpResponseException.class, () -> client.listSettings(new SettingSelector(), contextMock));
}
@Test
@@ -166,24 +170,24 @@ public void listFeatureFlagsTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenReturn(new PagedIterable<>(pagedFlux));
- assertEquals(configurations, client.listFeatureFlags(new SettingSelector(), false).getFeatureFlags());
+ assertEquals(configurations, client.listFeatureFlags(new SettingSelector(), contextMock).getFeatureFlags());
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any())).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
assertThrows(AppConfigurationStatusException.class,
- () -> client.listFeatureFlags(new SettingSelector(), false));
+ () -> client.listFeatureFlags(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(408);
assertThrows(AppConfigurationStatusException.class,
- () -> client.listFeatureFlags(new SettingSelector(), false));
+ () -> client.listFeatureFlags(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(500);
assertThrows(AppConfigurationStatusException.class,
- () -> client.listFeatureFlags(new SettingSelector(), false));
+ () -> client.listFeatureFlags(new SettingSelector(), contextMock));
when(responseMock.getStatusCode()).thenReturn(499);
- assertThrows(HttpResponseException.class, () -> client.listFeatureFlags(new SettingSelector(), false));
+ assertThrows(HttpResponseException.class, () -> client.listFeatureFlags(new SettingSelector(), contextMock));
}
@Test
@@ -192,7 +196,7 @@ public void listSettingsUnknownHostTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenThrow(new UncheckedIOException(new UnknownHostException()));
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettings(new SettingSelector(), contextMock));
}
@Test
@@ -202,7 +206,7 @@ public void listSettingsNoCredentialTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenThrow(new CredentialUnavailableException("No Credential"));
- assertThrows(CredentialUnavailableException.class, () -> client.listSettings(new SettingSelector(), false));
+ assertThrows(CredentialUnavailableException.class, () -> client.listSettings(new SettingSelector(), contextMock));
}
@Test
@@ -212,7 +216,7 @@ public void getWatchNoCredentialTest() {
when(clientMock.getConfigurationSettingWithResponse(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()))
.thenThrow(new CredentialUnavailableException("No Credential"));
- assertThrows(CredentialUnavailableException.class, () -> client.getWatchKey("key", "label", false));
+ assertThrows(CredentialUnavailableException.class, () -> client.getWatchKey("key", "label", contextMock));
}
@Test
@@ -238,7 +242,7 @@ public void backoffTest() {
when(clientMock.listConfigurationSettings(Mockito.any(SettingSelector.class), Mockito.any()))
.thenReturn(settingsMock);
- client.listSettings(new SettingSelector(), false);
+ client.listSettings(new SettingSelector(), contextMock);
assertTrue(client.getBackoffEndTime().isBefore(Instant.now()));
assertEquals(0, client.getFailedAttempts());
}
@@ -256,25 +260,25 @@ public void listSettingSnapshotTest() {
when(snapshotResponseMock.getValue()).thenReturn(snapshot);
when(clientMock.listConfigurationSettingsForSnapshot(Mockito.any())).thenReturn(settingsMock);
- assertEquals(configurations, client.listSettingSnapshot("SnapshotName", false));
+ assertEquals(configurations, client.listSettingSnapshot("SnapshotName", contextMock));
when(clientMock.listConfigurationSettingsForSnapshot(Mockito.any())).thenThrow(exceptionMock);
when(exceptionMock.getResponse()).thenReturn(responseMock);
when(responseMock.getStatusCode()).thenReturn(429);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", contextMock));
when(responseMock.getStatusCode()).thenReturn(408);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", contextMock));
when(responseMock.getStatusCode()).thenReturn(500);
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", contextMock));
when(responseMock.getStatusCode()).thenReturn(499);
- assertThrows(HttpResponseException.class, () -> client.listSettingSnapshot("SnapshotName", false));
+ assertThrows(HttpResponseException.class, () -> client.listSettingSnapshot("SnapshotName", contextMock));
when(clientMock.getSnapshotWithResponse(Mockito.any(), Mockito.any(), Mockito.any()))
.thenThrow(new UncheckedIOException(new UnknownHostException()));
- assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", false));
+ assertThrows(AppConfigurationStatusException.class, () -> client.listSettingSnapshot("SnapshotName", contextMock));
}
@Test
@@ -289,7 +293,7 @@ public void listSettingSnapshotInvalidCompositionTypeTest() {
when(snapshotResponseMock.getValue()).thenReturn(snapshot);
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> client.listSettingSnapshot("SnapshotName", false));
+ () -> client.listSettingSnapshot("SnapshotName", contextMock));
assertEquals("Snapshot SnapshotName needs to be of type Key.", e.getMessage());
}
@@ -324,7 +328,7 @@ public void checkWatchKeysTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any()))
.thenReturn(new PagedIterable<>(pagedFlux));
- assertTrue(client.checkWatchKeys(new SettingSelector(), false));
+ assertTrue(client.checkWatchKeys(new SettingSelector(), contextMock));
pagedResponse.close();
} catch (IOException e) {
e.printStackTrace();
@@ -338,7 +342,7 @@ public void checkWatchKeysTest() {
when(clientMock.listConfigurationSettings(Mockito.any(), Mockito.any())).thenReturn(new PagedIterable<>(pagedFlux));
- assertFalse(client.checkWatchKeys(new SettingSelector(), false));
+ assertFalse(client.checkWatchKeys(new SettingSelector(), contextMock));
pagedResponse.close();
} catch (IOException e) {
e.printStackTrace();
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
index 07c2e5a56fa2..92704d28e88b 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/FeatureFlagClientTest.java
@@ -33,6 +33,7 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagFilter;
@@ -43,6 +44,9 @@ public class FeatureFlagClientTest {
@Mock
private AppConfigurationReplicaClient clientMock;
+
+ @Mock
+ private Context contextMock;
private FeatureFlagClient featureFlagClient;
@@ -76,9 +80,9 @@ public void cleanup() throws Exception {
public void loadFeatureFlagsTestNoFeatureFlags() {
List settings = List.of(new ConfigurationSetting().setKey("FakeKey"));
FeatureFlags featureFlags = new FeatureFlags(null, settings);
- when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
+ when(clientMock.listFeatureFlags(Mockito.any(), Mockito.any(Context.class))).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, contextMock);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals("FakeKey", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -90,9 +94,9 @@ public void loadFeatureFlagsTestFeatureFlags() {
List settings = List.of(new FeatureFlagConfigurationSetting("Alpha", false),
new FeatureFlagConfigurationSetting("Beta", true));
FeatureFlags featureFlags = new FeatureFlags(null, settings);
- when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
+ when(clientMock.listFeatureFlags(Mockito.any(), Mockito.any(Context.class))).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, contextMock);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/Alpha", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -105,9 +109,9 @@ public void loadFeatureFlagsTestMultipleLoads() {
List settings = List.of(new FeatureFlagConfigurationSetting("Alpha", false),
new FeatureFlagConfigurationSetting("Beta", true));
FeatureFlags featureFlags = new FeatureFlags(null, settings);
- when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
+ when(clientMock.listFeatureFlags(Mockito.any(), Mockito.any(Context.class))).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, contextMock);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/Alpha", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -117,9 +121,9 @@ public void loadFeatureFlagsTestMultipleLoads() {
List settings2 = List.of(new FeatureFlagConfigurationSetting("Alpha", true),
new FeatureFlagConfigurationSetting("Gamma", false));
featureFlags = new FeatureFlags(null, settings2);
- when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
+ when(clientMock.listFeatureFlags(Mockito.any(), Mockito.any(Context.class))).thenReturn(featureFlags);
- featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
+ featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, contextMock);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/Alpha", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
@@ -163,9 +167,9 @@ public void loadFeatureFlagsTestTargetingFilter() {
targetingFlag.addClientFilter(targetingFilter);
List settings = List.of(targetingFlag);
FeatureFlags featureFlags = new FeatureFlags(null, settings);
- when(clientMock.listFeatureFlags(Mockito.any(), Mockito.anyBoolean())).thenReturn(featureFlags);
+ when(clientMock.listFeatureFlags(Mockito.any(), Mockito.any(Context.class))).thenReturn(featureFlags);
- List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, false);
+ List featureFlagsList = featureFlagClient.loadFeatureFlags(clientMock, null, emptyLabelList, contextMock);
assertEquals(1, featureFlagsList.size());
assertEquals(featureFlags, featureFlagsList.get(0));
assertEquals(".appconfig.featureflag/TargetingTest", featureFlagsList.get(0).getFeatureFlags().get(0).getKey());
diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
index 2bdcdd238590..f93ffa0c63e6 100644
--- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
+++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/http/policy/TracingInfoTest.java
@@ -24,37 +24,41 @@ public void getValueTest() {
Configuration configuration = getConfiguration("false");
TracingInfo tracingInfo = new TracingInfo(false, 0, configuration);
- assertEquals("RequestType=Startup", tracingInfo.getValue(false));
- assertEquals("RequestType=Watch", tracingInfo.getValue(true));
+ assertEquals("RequestType=Startup", tracingInfo.getValue(false, false));
+ assertEquals("RequestType=Watch", tracingInfo.getValue(true, false));
tracingInfo = new TracingInfo(true, 0, configuration);
- assertEquals("RequestType=Startup,UsesKeyVault", tracingInfo.getValue(false));
+ assertEquals("RequestType=Startup,UsesKeyVault", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 1, configuration);
- assertEquals("RequestType=Startup,ReplicaCount=1", tracingInfo.getValue(false));
+ assertEquals("RequestType=Startup,ReplicaCount=1", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 0, configuration);
tracingInfo.getFeatureFlagTracing().updateFeatureFilterTelemetry("Random");
- assertEquals("RequestType=Startup,Filter=CSTM", tracingInfo.getValue(false));
+ assertEquals("RequestType=Startup,Filter=CSTM", tracingInfo.getValue(false, false));
+
+ tracingInfo = new TracingInfo(true, 0, configuration);
+ assertEquals("RequestType=Startup,UsesKeyVault,PushRefresh", tracingInfo.getValue(false, true));
+
}
@Test
public void disableTracingTest() {
TracingInfo tracingInfo = new TracingInfo(false, 0, getConfiguration(null));
- assertNotEquals("", tracingInfo.getValue(false));
+ assertNotEquals("", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 0, getConfiguration(""));
- assertNotEquals("", tracingInfo.getValue(false));
+ assertNotEquals("", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 0, getConfiguration("true"));
- assertEquals("", tracingInfo.getValue(false));
+ assertEquals("", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 0, getConfiguration("false"));
- assertNotEquals("", tracingInfo.getValue(false));
+ assertNotEquals("", tracingInfo.getValue(false, false));
tracingInfo = new TracingInfo(false, 0, getConfiguration("random string"));
- assertNotEquals("", tracingInfo.getValue(false));
+ assertNotEquals("", tracingInfo.getValue(false, false));
}
private static final ConfigurationSource EMPTY_SOURCE = new ConfigurationSource() {
From cb2c36954b58dc0f4bd71a910327ff2cbf1c2228 Mon Sep 17 00:00:00 2001
From: Matthew Metcalf
Date: Thu, 13 Feb 2025 11:16:24 -0800
Subject: [PATCH 06/13] Update to use Evaluation Event (#44070)
---
.../FeatureManagementConfiguration.java | 18 ++++--
.../feature/management/FeatureManager.java | 45 +++++++++-----
.../filters/ContextualFeatureFilter.java | 1 +
.../filters/ContextualFeatureFilterAsync.java | 1 +
.../management/filters/TargetingFilter.java | 7 +--
.../implementation/FeatureFilterUtils.java | 1 +
.../FeatureManagementConstants.java | 4 +-
.../feature/management/models/Conditions.java | 5 ++
.../management/models/EvaluationEvent.java | 62 +++++++++++++++++++
.../feature/management/models/Feature.java | 13 ++--
.../models/FeatureManagementException.java | 2 +-
.../filters/TargetingFilterTest.java | 36 ++++++-----
.../TargetingFilterUtilsTest.java | 6 +-
13 files changed, 154 insertions(+), 47 deletions(-)
create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/EvaluationEvent.java
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManagementConfiguration.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManagementConfiguration.java
index 4b7ca99db962..97eb31aed40f 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManagementConfiguration.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManagementConfiguration.java
@@ -2,10 +2,13 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management;
+import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
@@ -15,7 +18,9 @@
*/
@Configuration
@EnableConfigurationProperties({ FeatureManagementConfigProperties.class, FeatureManagementProperties.class })
-class FeatureManagementConfiguration {
+class FeatureManagementConfiguration implements ApplicationContextAware {
+
+ private ApplicationContext appContext;
/**
* Creates Feature Manager
@@ -26,8 +31,13 @@ class FeatureManagementConfiguration {
* @return FeatureManager
*/
@Bean
- FeatureManager featureManager(ApplicationContext context,
- FeatureManagementProperties featureManagementConfigurations, FeatureManagementConfigProperties properties) {
- return new FeatureManager(context, featureManagementConfigurations, properties);
+ FeatureManager featureManager(FeatureManagementProperties featureManagementConfigurations,
+ FeatureManagementConfigProperties properties) {
+ return new FeatureManager(appContext, featureManagementConfigurations, properties);
+ }
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ this.appContext = applicationContext;
}
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
index 97f864d0060a..1e13cdc543e4 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java
@@ -23,6 +23,7 @@
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
import com.azure.spring.cloud.feature.management.models.Conditions;
+import com.azure.spring.cloud.feature.management.models.EvaluationEvent;
import com.azure.spring.cloud.feature.management.models.Feature;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.FilterNotFoundException;
@@ -42,8 +43,8 @@ public class FeatureManager {
private final FeatureManagementProperties featureManagementConfigurations;
private transient FeatureManagementConfigProperties properties;
-
- private static final Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(100);
+
+ private static final Duration DEFAULT_BLOCK_TIMEOUT = Duration.ofSeconds(100);
/**
* Can be called to check if a feature is enabled or disabled.
@@ -69,7 +70,7 @@ public class FeatureManager {
* @throws FilterNotFoundException file not found
*/
public Mono isEnabledAsync(String feature) {
- return checkFeature(feature, null);
+ return checkFeature(feature, null).map(event -> event.isEnabled());
}
/**
@@ -82,7 +83,7 @@ public Mono isEnabledAsync(String feature) {
* @throws FilterNotFoundException file not found
*/
public Boolean isEnabled(String feature) throws FilterNotFoundException {
- return checkFeature(feature, null).block(DEFAULT_REQUEST_TIMEOUT);
+ return checkFeature(feature, null).map(event -> event.isEnabled()).block(DEFAULT_BLOCK_TIMEOUT);
}
/**
@@ -96,7 +97,7 @@ public Boolean isEnabled(String feature) throws FilterNotFoundException {
* @throws FilterNotFoundException file not found
*/
public Mono isEnabledAsync(String feature, Object featureContext) {
- return checkFeature(feature, featureContext);
+ return checkFeature(feature, featureContext).map(event -> event.isEnabled());
}
/**
@@ -110,30 +111,40 @@ public Mono isEnabledAsync(String feature, Object featureContext) {
* @throws FilterNotFoundException file not found
*/
public Boolean isEnabled(String feature, Object featureContext) throws FilterNotFoundException {
- return checkFeature(feature, featureContext).block(DEFAULT_REQUEST_TIMEOUT);
+ return checkFeature(feature, featureContext).map(event -> event.isEnabled()).block(DEFAULT_BLOCK_TIMEOUT);
}
- private Mono checkFeature(String featureName, Object featureContext) throws FilterNotFoundException {
+ private Mono checkFeature(String featureName, Object featureContext)
+ throws FilterNotFoundException {
Feature featureFlag = featureManagementConfigurations.getFeatureFlags().stream()
.filter(feature -> feature.getId().equals(featureName)).findAny().orElse(null);
+ EvaluationEvent event = new EvaluationEvent(featureFlag);
+
if (featureFlag == null) {
- return Mono.just(false);
+ LOGGER.warn("Feature flag %s not found", featureName);
+ return Mono.just(event);
}
- if (featureFlag.getConditions().getClientFilters().size() == 0) {
- return Mono.just(featureFlag.isEnabled());
+ if (!featureFlag.isEnabled()) {
+ // If a feature flag is disabled and override can't enable it
+ return Mono.just(event.setEnabled(false));
}
- return checkFeatureFilters(featureFlag, featureContext);
+ Mono result = this.checkFeatureFilters(event, featureContext);
+
+ return result;
}
- private Mono checkFeatureFilters(Feature featureFlag, Object featureContext) {
+ private Mono checkFeatureFilters(EvaluationEvent event, Object featureContext) {
+ Feature featureFlag = event.getFeature();
Conditions conditions = featureFlag.getConditions();
List featureFilters = conditions.getClientFilters();
if (featureFilters.size() == 0) {
- return Mono.just(true);
+ return Mono.just(event.setEnabled(true));
+ } else {
+ event.setEnabled(conditions.getRequirementType().equals(ALL_REQUIREMENT_TYPE));
}
List> filterResults = new ArrayList>();
@@ -165,10 +176,14 @@ private Mono checkFeatureFilters(Feature featureFlag, Object featureCon
}
if (ALL_REQUIREMENT_TYPE.equals(featureFlag.getConditions().getRequirementType())) {
- return Flux.merge(filterResults).reduce((a, b) -> a && b).single();
+ return Flux.merge(filterResults).reduce((a, b) -> {
+ return a && b;
+ }).single().map(result -> {
+ return event.setEnabled(result);
+ });
}
// Any Filter must be true
- return Flux.merge(filterResults).reduce((a, b) -> a || b).single();
+ return Flux.merge(filterResults).reduce((a, b) -> a || b).single().map(result -> event.setEnabled(result));
}
/**
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
index 8432c1fbc3a9..81fd09d0fe11 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java
@@ -8,6 +8,7 @@
* A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
* feature management. As a Contextual feature filter any context that is passed in to the feature request will be
* passed along to the filter(s).
+ * @since 6.0.0
*/
@FunctionalInterface
public interface ContextualFeatureFilter {
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
index 6725e88ca6b7..c22930312978 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java
@@ -10,6 +10,7 @@
* A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
* feature management. As a Contextual feature filter any context that is passed in to the feature request will be
* passed along to the filter(s).
+ * @since 6.0.0
*/
@FunctionalInterface
public interface ContextualFeatureFilterAsync {
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
index fcaa357aa947..5d1285aaee06 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java
@@ -52,7 +52,7 @@ public class TargetingFilter implements FeatureFilter, ContextualFeatureFilter {
* Audience that always returns false
*/
private static final String EXCLUSION_CAMEL = "Exclusion";
- protected static final String EXCLUSION = "Exclusion";
+
/**
* Error message for when the total Audience value is greater than 100 percent.
*/
@@ -111,7 +111,6 @@ public boolean evaluate(FeatureFilterEvaluationContext context, Object appContex
}
TargetingContext targetingContext = new TargetingFilterContext();
-
if (appContext != null && appContext instanceof TargetingContext) {
// Use this if, there is an appContext + the contextualAccessor, or there is no contextAccessor.
targetingContext = (TargetingContext) appContext;
@@ -151,10 +150,10 @@ public boolean evaluate(FeatureFilterEvaluationContext context, Object appContex
if (exclusionMap == null) {
exclusionMap = new HashMap<>();
}
-
+
Object users = exclusionMap.get(exclusionUserValue);
Object groups = exclusionMap.get(exclusionGroupsValue);
-
+
Map exclusion = new HashMap<>();
if (users instanceof Map) {
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
index cb25caac6203..5f8fe1cf9925 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java
@@ -6,6 +6,7 @@
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
index 64165c5ddedd..605d45ea1dfe 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java
@@ -2,10 +2,10 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management.implementation;
-public class FeatureManagementConstants {
+public final class FeatureManagementConstants {
public static final String DEFAULT_REQUIREMENT_TYPE = "Any";
-
+
public static final String ALL_REQUIREMENT_TYPE = "All";
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Conditions.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Conditions.java
index a6504e0f219d..069c6bbafab7 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Conditions.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Conditions.java
@@ -11,6 +11,9 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Conditions for evaluating a feature flag.
+ */
@JsonIgnoreProperties(ignoreUnknown = true)
public class Conditions {
@JsonProperty("client_filters")
@@ -28,6 +31,7 @@ public String getRequirementType() {
/**
* @param requirementType the requirementType to set
+ * @return Conditions
*/
public Conditions setRequirementType(String requirementType) {
this.requirementType = requirementType;
@@ -43,6 +47,7 @@ public List getClientFilters() {
/**
* @param clientFilters the clientFilters to set
+ * @return Conditions
*/
public Conditions setClientFilters(List clientFilters) {
this.clientFilters = clientFilters;
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/EvaluationEvent.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/EvaluationEvent.java
new file mode 100644
index 000000000000..786269d9ed6b
--- /dev/null
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/EvaluationEvent.java
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.cloud.feature.management.models;
+
+/**
+ * Event tracking the evaluation of a feature flag
+ */
+public class EvaluationEvent {
+
+ private final Feature feature;
+
+ private String user = "";
+
+ private boolean enabled = false;
+
+ /**
+ * Creates an Evaluation Event for the given feature
+ * @param feature Feature
+ */
+ public EvaluationEvent(Feature feature) {
+ this.feature = feature;
+ }
+
+ /**
+ * @return the feature
+ */
+ public Feature getFeature() {
+ return feature;
+ }
+
+ /**
+ * @return the user
+ */
+ public String getUser() {
+ return user;
+ }
+
+ /**
+ * @param user the user to set
+ * @return EvaluationEvent
+ */
+ public EvaluationEvent setUser(String user) {
+ this.user = user;
+ return this;
+ }
+
+ /**
+ * @return the enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * @param enabled the enabled to set
+ * @return EvaluationEvent
+ */
+ public EvaluationEvent setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
index e3b43b0013c4..e25f75b77278 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java
@@ -35,6 +35,7 @@ public String getId() {
/**
* @param id the id to set
+ * @return Feature
*/
public Feature setId(String id) {
this.id = id;
@@ -50,6 +51,7 @@ public boolean isEnabled() {
/**
* @param enabled the enabled to set
+ * @return Feature
*/
public Feature setEnabled(boolean enabled) {
this.enabled = enabled;
@@ -58,14 +60,15 @@ public Feature setEnabled(boolean enabled) {
/**
* @return the description
- * */
+ */
public String getDescription() {
return description;
}
/**
* @param description the description to set
- * */
+ * @return Feature
+ */
public Feature setDescription(String description) {
this.description = description;
return this;
@@ -73,17 +76,17 @@ public Feature setDescription(String description) {
/**
* @return the conditions
- * */
+ */
public Conditions getConditions() {
return conditions;
}
/**
* @param conditions the conditions to set
- * */
+ * @return Feature
+ */
public Feature setConditions(Conditions conditions) {
this.conditions = conditions;
return this;
}
-
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/FeatureManagementException.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/FeatureManagementException.java
index 8204b27cabfa..30ce0305f987 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/FeatureManagementException.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/FeatureManagementException.java
@@ -20,7 +20,7 @@ public final class FeatureManagementException extends RuntimeException {
*
* @param message the error message.
*/
- FeatureManagementException(String message) {
+ public FeatureManagementException(String message) {
super(message);
this.message = message;
}
diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
index 660e2b774fab..26115986d3fe 100644
--- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
+++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java
@@ -62,23 +62,25 @@ public void targetedUser() {
}
@Test
- public void targetedUserLower() {
+ public void targetedUserAudience() {
FeatureFilterEvaluationContext context = new FeatureFilterEvaluationContext();
- Map