Skip to content

Commit cb2c369

Browse files
authored
Update to use Evaluation Event (#44070)
1 parent a4de2bc commit cb2c369

File tree

13 files changed

+154
-47
lines changed

13 files changed

+154
-47
lines changed

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManagementConfiguration.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// Licensed under the MIT License.
33
package com.azure.spring.cloud.feature.management;
44

5+
import org.springframework.beans.BeansException;
56
import org.springframework.boot.context.properties.EnableConfigurationProperties;
67
import org.springframework.context.ApplicationContext;
8+
import org.springframework.context.ApplicationContextAware;
79
import org.springframework.context.annotation.Bean;
810
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.lang.NonNull;
912

1013
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
1114
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
@@ -15,7 +18,9 @@
1518
*/
1619
@Configuration
1720
@EnableConfigurationProperties({ FeatureManagementConfigProperties.class, FeatureManagementProperties.class })
18-
class FeatureManagementConfiguration {
21+
class FeatureManagementConfiguration implements ApplicationContextAware {
22+
23+
private ApplicationContext appContext;
1924

2025
/**
2126
* Creates Feature Manager
@@ -26,8 +31,13 @@ class FeatureManagementConfiguration {
2631
* @return FeatureManager
2732
*/
2833
@Bean
29-
FeatureManager featureManager(ApplicationContext context,
30-
FeatureManagementProperties featureManagementConfigurations, FeatureManagementConfigProperties properties) {
31-
return new FeatureManager(context, featureManagementConfigurations, properties);
34+
FeatureManager featureManager(FeatureManagementProperties featureManagementConfigurations,
35+
FeatureManagementConfigProperties properties) {
36+
return new FeatureManager(appContext, featureManagementConfigurations, properties);
37+
}
38+
39+
@Override
40+
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
41+
this.appContext = applicationContext;
3242
}
3343
}

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
2424
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
2525
import com.azure.spring.cloud.feature.management.models.Conditions;
26+
import com.azure.spring.cloud.feature.management.models.EvaluationEvent;
2627
import com.azure.spring.cloud.feature.management.models.Feature;
2728
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
2829
import com.azure.spring.cloud.feature.management.models.FilterNotFoundException;
@@ -42,8 +43,8 @@ public class FeatureManager {
4243
private final FeatureManagementProperties featureManagementConfigurations;
4344

4445
private transient FeatureManagementConfigProperties properties;
45-
46-
private static final Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(100);
46+
47+
private static final Duration DEFAULT_BLOCK_TIMEOUT = Duration.ofSeconds(100);
4748

4849
/**
4950
* Can be called to check if a feature is enabled or disabled.
@@ -69,7 +70,7 @@ public class FeatureManager {
6970
* @throws FilterNotFoundException file not found
7071
*/
7172
public Mono<Boolean> isEnabledAsync(String feature) {
72-
return checkFeature(feature, null);
73+
return checkFeature(feature, null).map(event -> event.isEnabled());
7374
}
7475

7576
/**
@@ -82,7 +83,7 @@ public Mono<Boolean> isEnabledAsync(String feature) {
8283
* @throws FilterNotFoundException file not found
8384
*/
8485
public Boolean isEnabled(String feature) throws FilterNotFoundException {
85-
return checkFeature(feature, null).block(DEFAULT_REQUEST_TIMEOUT);
86+
return checkFeature(feature, null).map(event -> event.isEnabled()).block(DEFAULT_BLOCK_TIMEOUT);
8687
}
8788

8889
/**
@@ -96,7 +97,7 @@ public Boolean isEnabled(String feature) throws FilterNotFoundException {
9697
* @throws FilterNotFoundException file not found
9798
*/
9899
public Mono<Boolean> isEnabledAsync(String feature, Object featureContext) {
99-
return checkFeature(feature, featureContext);
100+
return checkFeature(feature, featureContext).map(event -> event.isEnabled());
100101
}
101102

102103
/**
@@ -110,30 +111,40 @@ public Mono<Boolean> isEnabledAsync(String feature, Object featureContext) {
110111
* @throws FilterNotFoundException file not found
111112
*/
112113
public Boolean isEnabled(String feature, Object featureContext) throws FilterNotFoundException {
113-
return checkFeature(feature, featureContext).block(DEFAULT_REQUEST_TIMEOUT);
114+
return checkFeature(feature, featureContext).map(event -> event.isEnabled()).block(DEFAULT_BLOCK_TIMEOUT);
114115
}
115116

116-
private Mono<Boolean> checkFeature(String featureName, Object featureContext) throws FilterNotFoundException {
117+
private Mono<EvaluationEvent> checkFeature(String featureName, Object featureContext)
118+
throws FilterNotFoundException {
117119
Feature featureFlag = featureManagementConfigurations.getFeatureFlags().stream()
118120
.filter(feature -> feature.getId().equals(featureName)).findAny().orElse(null);
119121

122+
EvaluationEvent event = new EvaluationEvent(featureFlag);
123+
120124
if (featureFlag == null) {
121-
return Mono.just(false);
125+
LOGGER.warn("Feature flag %s not found", featureName);
126+
return Mono.just(event);
122127
}
123128

124-
if (featureFlag.getConditions().getClientFilters().size() == 0) {
125-
return Mono.just(featureFlag.isEnabled());
129+
if (!featureFlag.isEnabled()) {
130+
// If a feature flag is disabled and override can't enable it
131+
return Mono.just(event.setEnabled(false));
126132
}
127133

128-
return checkFeatureFilters(featureFlag, featureContext);
134+
Mono<EvaluationEvent> result = this.checkFeatureFilters(event, featureContext);
135+
136+
return result;
129137
}
130138

131-
private Mono<Boolean> checkFeatureFilters(Feature featureFlag, Object featureContext) {
139+
private Mono<EvaluationEvent> checkFeatureFilters(EvaluationEvent event, Object featureContext) {
140+
Feature featureFlag = event.getFeature();
132141
Conditions conditions = featureFlag.getConditions();
133142
List<FeatureFilterEvaluationContext> featureFilters = conditions.getClientFilters();
134143

135144
if (featureFilters.size() == 0) {
136-
return Mono.just(true);
145+
return Mono.just(event.setEnabled(true));
146+
} else {
147+
event.setEnabled(conditions.getRequirementType().equals(ALL_REQUIREMENT_TYPE));
137148
}
138149

139150
List<Mono<Boolean>> filterResults = new ArrayList<Mono<Boolean>>();
@@ -165,10 +176,14 @@ private Mono<Boolean> checkFeatureFilters(Feature featureFlag, Object featureCon
165176
}
166177

167178
if (ALL_REQUIREMENT_TYPE.equals(featureFlag.getConditions().getRequirementType())) {
168-
return Flux.merge(filterResults).reduce((a, b) -> a && b).single();
179+
return Flux.merge(filterResults).reduce((a, b) -> {
180+
return a && b;
181+
}).single().map(result -> {
182+
return event.setEnabled(result);
183+
});
169184
}
170185
// Any Filter must be true
171-
return Flux.merge(filterResults).reduce((a, b) -> a || b).single();
186+
return Flux.merge(filterResults).reduce((a, b) -> a || b).single().map(result -> event.setEnabled(result));
172187
}
173188

174189
/**

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
99
* feature management. As a Contextual feature filter any context that is passed in to the feature request will be
1010
* passed along to the filter(s).
11+
* @since 6.0.0
1112
*/
1213
@FunctionalInterface
1314
public interface ContextualFeatureFilter {

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/ContextualFeatureFilterAsync.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* A Filter for Feature Management that is attached to Features. The filter needs to have @Component set to be found by
1111
* feature management. As a Contextual feature filter any context that is passed in to the feature request will be
1212
* passed along to the filter(s).
13+
* @since 6.0.0
1314
*/
1415
@FunctionalInterface
1516
public interface ContextualFeatureFilterAsync {

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/filters/TargetingFilter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class TargetingFilter implements FeatureFilter, ContextualFeatureFilter {
5252
* Audience that always returns false
5353
*/
5454
private static final String EXCLUSION_CAMEL = "Exclusion";
55-
protected static final String EXCLUSION = "Exclusion";
55+
5656
/**
5757
* Error message for when the total Audience value is greater than 100 percent.
5858
*/
@@ -111,7 +111,6 @@ public boolean evaluate(FeatureFilterEvaluationContext context, Object appContex
111111
}
112112

113113
TargetingContext targetingContext = new TargetingFilterContext();
114-
115114
if (appContext != null && appContext instanceof TargetingContext) {
116115
// Use this if, there is an appContext + the contextualAccessor, or there is no contextAccessor.
117116
targetingContext = (TargetingContext) appContext;
@@ -151,10 +150,10 @@ public boolean evaluate(FeatureFilterEvaluationContext context, Object appContex
151150
if (exclusionMap == null) {
152151
exclusionMap = new HashMap<>();
153152
}
154-
153+
155154
Object users = exclusionMap.get(exclusionUserValue);
156155
Object groups = exclusionMap.get(exclusionGroupsValue);
157-
156+
158157
Map<String, Object> exclusion = new HashMap<>();
159158

160159
if (users instanceof Map) {

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureFilterUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.math.BigInteger;
77
import java.security.MessageDigest;
88
import java.security.NoSuchAlgorithmException;
9+
import java.util.ArrayList;
910
import java.util.Collection;
1011
import java.util.Map;
1112

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/implementation/FeatureManagementConstants.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Licensed under the MIT License.
33
package com.azure.spring.cloud.feature.management.implementation;
44

5-
public class FeatureManagementConstants {
5+
public final class FeatureManagementConstants {
66

77
public static final String DEFAULT_REQUIREMENT_TYPE = "Any";
8-
8+
99
public static final String ALL_REQUIREMENT_TYPE = "All";
1010

1111
}

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Conditions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
1212
import com.fasterxml.jackson.annotation.JsonProperty;
1313

14+
/**
15+
* Conditions for evaluating a feature flag.
16+
*/
1417
@JsonIgnoreProperties(ignoreUnknown = true)
1518
public class Conditions {
1619
@JsonProperty("client_filters")
@@ -28,6 +31,7 @@ public String getRequirementType() {
2831

2932
/**
3033
* @param requirementType the requirementType to set
34+
* @return Conditions
3135
*/
3236
public Conditions setRequirementType(String requirementType) {
3337
this.requirementType = requirementType;
@@ -43,6 +47,7 @@ public List<FeatureFilterEvaluationContext> getClientFilters() {
4347

4448
/**
4549
* @param clientFilters the clientFilters to set
50+
* @return Conditions
4651
*/
4752
public Conditions setClientFilters(List<FeatureFilterEvaluationContext> clientFilters) {
4853
this.clientFilters = clientFilters;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.spring.cloud.feature.management.models;
4+
5+
/**
6+
* Event tracking the evaluation of a feature flag
7+
*/
8+
public class EvaluationEvent {
9+
10+
private final Feature feature;
11+
12+
private String user = "";
13+
14+
private boolean enabled = false;
15+
16+
/**
17+
* Creates an Evaluation Event for the given feature
18+
* @param feature Feature
19+
*/
20+
public EvaluationEvent(Feature feature) {
21+
this.feature = feature;
22+
}
23+
24+
/**
25+
* @return the feature
26+
*/
27+
public Feature getFeature() {
28+
return feature;
29+
}
30+
31+
/**
32+
* @return the user
33+
*/
34+
public String getUser() {
35+
return user;
36+
}
37+
38+
/**
39+
* @param user the user to set
40+
* @return EvaluationEvent
41+
*/
42+
public EvaluationEvent setUser(String user) {
43+
this.user = user;
44+
return this;
45+
}
46+
47+
/**
48+
* @return the enabled
49+
*/
50+
public boolean isEnabled() {
51+
return enabled;
52+
}
53+
54+
/**
55+
* @param enabled the enabled to set
56+
* @return EvaluationEvent
57+
*/
58+
public EvaluationEvent setEnabled(boolean enabled) {
59+
this.enabled = enabled;
60+
return this;
61+
}
62+
}

sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/models/Feature.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public String getId() {
3535

3636
/**
3737
* @param id the id to set
38+
* @return Feature
3839
*/
3940
public Feature setId(String id) {
4041
this.id = id;
@@ -50,6 +51,7 @@ public boolean isEnabled() {
5051

5152
/**
5253
* @param enabled the enabled to set
54+
* @return Feature
5355
*/
5456
public Feature setEnabled(boolean enabled) {
5557
this.enabled = enabled;
@@ -58,32 +60,33 @@ public Feature setEnabled(boolean enabled) {
5860

5961
/**
6062
* @return the description
61-
* */
63+
*/
6264
public String getDescription() {
6365
return description;
6466
}
6567

6668
/**
6769
* @param description the description to set
68-
* */
70+
* @return Feature
71+
*/
6972
public Feature setDescription(String description) {
7073
this.description = description;
7174
return this;
7275
}
7376

7477
/**
7578
* @return the conditions
76-
* */
79+
*/
7780
public Conditions getConditions() {
7881
return conditions;
7982
}
8083

8184
/**
8285
* @param conditions the conditions to set
83-
* */
86+
* @return Feature
87+
*/
8488
public Feature setConditions(Conditions conditions) {
8589
this.conditions = conditions;
8690
return this;
8791
}
88-
8992
}

0 commit comments

Comments
 (0)