99import com .flagsmith .flagengine .Engine ;
1010import com .flagsmith .flagengine .EvaluationContext ;
1111import com .flagsmith .flagengine .EvaluationResult ;
12+ import com .flagsmith .flagengine .SegmentRule ;
13+ import com .flagsmith .flagengine .Segments ;
1214import com .flagsmith .interfaces .FlagsmithCache ;
1315import com .flagsmith .interfaces .FlagsmithSdk ;
1416import com .flagsmith .mappers .EngineMappers ;
3032import java .util .stream .Collectors ;
3133import lombok .AccessLevel ;
3234import lombok .Data ;
35+ import lombok .EqualsAndHashCode ;
3336import lombok .Getter ;
3437import lombok .NonNull ;
3538import lombok .Setter ;
39+ import lombok .ToString ;
3640import org .apache .commons .lang3 .StringUtils ;
3741import org .slf4j .Logger ;
3842import org .slf4j .LoggerFactory ;
4347@ Data
4448public class FlagsmithClient {
4549
50+ private static final List <String > IDENTITY_FREE_PROPERTY_PREFIXES = List .of (
51+ "$.environment." , "$.environment[" , "$.flags." , "$.flags[" );
52+
4653 private final FlagsmithLogger logger = new FlagsmithLogger ();
4754 private FlagsmithSdk flagsmithSdk ;
4855 private EvaluationContext evaluationContext ;
56+ @ Getter (AccessLevel .NONE )
57+ @ Setter (AccessLevel .NONE )
58+ @ EqualsAndHashCode .Exclude
59+ @ ToString .Exclude
60+ private EvaluationContext environmentEvaluationContext ;
4961 private PollingManager pollingManager ;
5062 @ Getter (AccessLevel .PACKAGE )
5163 @ Setter (AccessLevel .NONE )
@@ -58,6 +70,19 @@ public static FlagsmithClient.Builder newBuilder() {
5870 return new FlagsmithClient .Builder ();
5971 }
6072
73+ /**
74+ * Set the evaluation context, and derive the one environment flags evaluate against.
75+ *
76+ * @param evaluationContext the evaluation context
77+ */
78+ public void setEvaluationContext (EvaluationContext evaluationContext ) {
79+ this .environmentEvaluationContext = evaluationContext == null
80+ ? null
81+ : new EvaluationContext (evaluationContext )
82+ .withSegments (getIdentityFreeSegments (evaluationContext .getSegments ()));
83+ this .evaluationContext = evaluationContext ;
84+ }
85+
6186 /**
6287 * Load the environment flags in the environment variable from the API.
6388 */
@@ -68,7 +93,7 @@ public void updateEnvironment() {
6893 // if we didn't get an environment from the API,
6994 // then don't overwrite the copy we already have.
7095 if (updatedEvaluationContext != null ) {
71- this . evaluationContext = updatedEvaluationContext ;
96+ setEvaluationContext ( updatedEvaluationContext ) ;
7297 } else {
7398 logger .error (getEnvironmentUpdateErrorMessage ());
7499 }
@@ -411,24 +436,48 @@ private EventProcessor requireEventProcessor(String action) {
411436 }
412437
413438 private Flags getEnvironmentFlagsFromEvaluationContext () throws FlagsmithClientError {
414- if (evaluationContext == null ) {
439+ final EvaluationContext context = environmentEvaluationContext ;
440+ if (context == null ) {
415441 if (getConfig ().getFlagsmithFlagDefaults () == null ) {
416442 throw new FlagsmithClientError ("Unable to get flags. No environment present." );
417443 }
418444 return getDefaultFlags ();
419445 }
420446
421- final EvaluationResult result = Engine .getEvaluationResult (
422- new EvaluationContext (evaluationContext )
423- .withSegments (null )
424- );
447+ final EvaluationResult result = Engine .getEvaluationResult (context );
425448
426449 return Flags .fromEvaluationResult (
427450 result ,
428451 getConfig ().getAnalyticsProcessor (),
429452 getConfig ().getFlagsmithFlagDefaults ());
430453 }
431454
455+ /*
456+ * The segments that resolve without an identity, i.e. those whose every
457+ * condition reads only the environment or other flags.
458+ */
459+ private static Segments getIdentityFreeSegments (Segments segments ) {
460+ if (segments == null ) {
461+ return null ;
462+ }
463+ Segments identityFreeSegments = new Segments ();
464+ segments .getAdditionalProperties ().forEach ((segmentKey , segmentContext ) -> {
465+ if (isIdentityFree (segmentContext .getRules ())) {
466+ identityFreeSegments .setAdditionalProperty (segmentKey , segmentContext );
467+ }
468+ });
469+ return identityFreeSegments ;
470+ }
471+
472+ private static boolean isIdentityFree (List <SegmentRule > rules ) {
473+ return rules == null || rules .stream ().allMatch (rule ->
474+ (rule .getConditions () == null || rule .getConditions ().stream ().allMatch (condition ->
475+ condition .getProperty () != null
476+ && IDENTITY_FREE_PROPERTY_PREFIXES .stream ()
477+ .anyMatch (condition .getProperty ()::startsWith )))
478+ && isIdentityFree (rule .getRules ()));
479+ }
480+
432481 private Flags getIdentityFlagsFromEvaluationContext (
433482 String identifier , Map <String , Object > traits )
434483 throws FlagsmithClientError {
@@ -748,8 +797,8 @@ public FlagsmithClient build() {
748797 throw new FlagsmithRuntimeError (
749798 "Cannot use both default flag handler and offline handler." );
750799 }
751- client .evaluationContext = EngineMappers .mapEnvironmentToContext (
752- configuration .getOfflineHandler ().getEnvironment ());
800+ client .setEvaluationContext ( EngineMappers .mapEnvironmentToContext (
801+ configuration .getOfflineHandler ().getEnvironment () ));
753802 }
754803
755804 EventProcessor processor = null ;
0 commit comments