22// Licensed under the MIT License.
33package com .azure .spring .cloud .feature .management ;
44
5+ import static com .azure .spring .cloud .feature .management .implementation .FeatureManagementConstants .ALL_REQUIREMENT_TYPE ;
6+
7+ import java .time .Duration ;
8+ import java .util .ArrayList ;
59import java .util .HashSet ;
6- import java .util .Objects ;
10+ import java .util .List ;
711import java .util .Set ;
8- import java .util .stream .Stream ;
912
1013import org .slf4j .Logger ;
1114import org .slf4j .LoggerFactory ;
1215import org .springframework .beans .factory .NoSuchBeanDefinitionException ;
1316import org .springframework .context .ApplicationContext ;
1417import org .springframework .util .ReflectionUtils ;
1518
19+ import com .azure .spring .cloud .feature .management .filters .ContextualFeatureFilter ;
20+ import com .azure .spring .cloud .feature .management .filters .ContextualFeatureFilterAsync ;
1621import com .azure .spring .cloud .feature .management .filters .FeatureFilter ;
22+ import com .azure .spring .cloud .feature .management .filters .FeatureFilterAsync ;
1723import com .azure .spring .cloud .feature .management .implementation .FeatureManagementConfigProperties ;
1824import com .azure .spring .cloud .feature .management .implementation .FeatureManagementProperties ;
25+ import com .azure .spring .cloud .feature .management .models .Conditions ;
1926import com .azure .spring .cloud .feature .management .models .Feature ;
2027import com .azure .spring .cloud .feature .management .models .FeatureFilterEvaluationContext ;
2128import com .azure .spring .cloud .feature .management .models .FilterNotFoundException ;
2229
30+ import reactor .core .publisher .Flux ;
2331import reactor .core .publisher .Mono ;
2432
2533/**
@@ -34,6 +42,8 @@ public class FeatureManager {
3442 private final FeatureManagementProperties featureManagementConfigurations ;
3543
3644 private transient FeatureManagementConfigProperties properties ;
45+
46+ private static final Duration DEFAULT_REQUEST_TIMEOUT = Duration .ofSeconds (100 );
3747
3848 /**
3949 * Can be called to check if a feature is enabled or disabled.
@@ -59,7 +69,7 @@ public class FeatureManager {
5969 * @throws FilterNotFoundException file not found
6070 */
6171 public Mono <Boolean > isEnabledAsync (String feature ) {
62- return Mono . just ( checkFeature (feature ) );
72+ return checkFeature (feature , null );
6373 }
6474
6575 /**
@@ -72,48 +82,93 @@ public Mono<Boolean> isEnabledAsync(String feature) {
7282 * @throws FilterNotFoundException file not found
7383 */
7484 public Boolean isEnabled (String feature ) throws FilterNotFoundException {
75- return checkFeature (feature );
85+ return checkFeature (feature , null ).block (DEFAULT_REQUEST_TIMEOUT );
86+ }
87+
88+ /**
89+ * Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
90+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
91+ * isn't found it returns false.
92+ *
93+ * @param feature Feature being checked.
94+ * @param featureContext Local context
95+ * @return state of the feature
96+ * @throws FilterNotFoundException file not found
97+ */
98+ public Mono <Boolean > isEnabledAsync (String feature , Object featureContext ) {
99+ return checkFeature (feature , featureContext );
76100 }
77101
78- private boolean checkFeature (String featureName ) throws FilterNotFoundException {
102+ /**
103+ * Checks to see if the feature is enabled. If enabled it checks each filter, once a single filter returns true it
104+ * returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
105+ * isn't found it returns false.
106+ *
107+ * @param feature Feature being checked.
108+ * @param featureContext Local context
109+ * @return state of the feature
110+ * @throws FilterNotFoundException file not found
111+ */
112+ public Boolean isEnabled (String feature , Object featureContext ) throws FilterNotFoundException {
113+ return checkFeature (feature , featureContext ).block (DEFAULT_REQUEST_TIMEOUT );
114+ }
115+
116+ private Mono <Boolean > checkFeature (String featureName , Object featureContext ) throws FilterNotFoundException {
79117 Feature featureFlag = featureManagementConfigurations .getFeatureFlags ().stream ()
80118 .filter (feature -> feature .getId ().equals (featureName )).findAny ().orElse (null );
81119
82120 if (featureFlag == null ) {
83- return false ;
121+ return Mono . just ( false ) ;
84122 }
85123
86- Stream <FeatureFilterEvaluationContext > filters = featureFlag .getConditions ().getClientFilters ().stream ()
87- .filter (Objects ::nonNull ).filter (featureFilter -> featureFilter .getName () != null );
88-
89124 if (featureFlag .getConditions ().getClientFilters ().size () == 0 ) {
90- return featureFlag .isEnabled ();
91- }
92-
93- // All Filters must be true
94- if (featureFlag .getConditions ().getRequirementType ().equals ("All" )) {
95- return filters .allMatch (featureFilter -> isFeatureOn (featureFilter , featureName ));
125+ return Mono .just (featureFlag .isEnabled ());
96126 }
97127
98- // Any Filter must be true
99- return filters .anyMatch (featureFilter -> isFeatureOn (featureFilter , featureName ));
128+ return checkFeatureFilters (featureFlag , featureContext );
100129 }
101130
102- private boolean isFeatureOn (FeatureFilterEvaluationContext filter , String feature ) {
103- try {
104- FeatureFilter featureFilter = (FeatureFilter ) context .getBean (filter .getName ());
105- filter .setFeatureName (feature );
106-
107- return featureFilter .evaluate (filter );
108- } catch (NoSuchBeanDefinitionException e ) {
109- LOGGER .error ("Was unable to find Filter {}. Does the class exist and set as an @Component?" ,
110- filter .getName ());
111- if (properties .isFailFast ()) {
112- String message = "Fail fast is set and a Filter was unable to be found" ;
113- ReflectionUtils .rethrowRuntimeException (new FilterNotFoundException (message , e , filter ));
131+ private Mono <Boolean > checkFeatureFilters (Feature featureFlag , Object featureContext ) {
132+ Conditions conditions = featureFlag .getConditions ();
133+ List <FeatureFilterEvaluationContext > featureFilters = conditions .getClientFilters ();
134+
135+ if (featureFilters .size () == 0 ) {
136+ return Mono .just (true );
137+ }
138+
139+ List <Mono <Boolean >> filterResults = new ArrayList <Mono <Boolean >>();
140+ for (FeatureFilterEvaluationContext featureFilter : featureFilters ) {
141+ String filterName = featureFilter .getName ();
142+
143+ try {
144+ Object filter = context .getBean (filterName );
145+ featureFilter .setFeatureName (featureFlag .getId ());
146+ if (filter instanceof FeatureFilter ) {
147+ filterResults .add (Mono .just (((FeatureFilter ) filter ).evaluate (featureFilter )));
148+ } else if (filter instanceof ContextualFeatureFilter ) {
149+ filterResults
150+ .add (Mono .just (((ContextualFeatureFilter ) filter ).evaluate (featureFilter , featureContext )));
151+ } else if (filter instanceof FeatureFilterAsync ) {
152+ filterResults .add (((FeatureFilterAsync ) filter ).evaluateAsync (featureFilter ));
153+ } else if (filter instanceof ContextualFeatureFilterAsync ) {
154+ filterResults
155+ .add (((ContextualFeatureFilterAsync ) filter ).evaluateAsync (featureFilter , featureContext ));
156+ }
157+ } catch (NoSuchBeanDefinitionException e ) {
158+ LOGGER .error ("Was unable to find Filter {}. Does the class exist and set as an @Component?" ,
159+ filterName );
160+ if (properties .isFailFast ()) {
161+ String message = "Fail fast is set and a Filter was unable to be found" ;
162+ ReflectionUtils .rethrowRuntimeException (new FilterNotFoundException (message , e , featureFilter ));
163+ }
114164 }
115165 }
116- return false ;
166+
167+ if (ALL_REQUIREMENT_TYPE .equals (featureFlag .getConditions ().getRequirementType ())) {
168+ return Flux .merge (filterResults ).reduce ((a , b ) -> a && b ).single ();
169+ }
170+ // Any Filter must be true
171+ return Flux .merge (filterResults ).reduce ((a , b ) -> a || b ).single ();
117172 }
118173
119174 /**
0 commit comments