Skip to content

Commit 3f75c4e

Browse files
rathovarun1032Athira MVarun Rathore
authored
Ssrc bugbash fix (#1117)
* Handle empty context * fix issue related to update time * fix string equality * fix textcase * Fix lint errors * Add unit tests * fix for [438426692](getDouble() logs a malformed warning on type conversion failure) Using getDouble on a string parameter value, returns the appropriate default static value but logs a warning which looks incorrect ("%s" in the warning message?). * Update ServerTemplateResponse.java to fix b/438607881 In the server template builder flow using cached template, evaluation using custom signals is not working as intended. * Update getServerRemoteConfig.json to fix b/438607881 * Update getServerTemplateData.json to fix b/438607881 * fix for bugs * Resolve comment related to revert of ServerVersion Class * remove serverVersion * Resolve comments related to Evaluator * fix indentation * fix indentation * fix indentations * fix multi line indent * fix multi line indents * Update ConditionEvaluator.java * Update ConditionEvaluator.java --------- Co-authored-by: Athira M <athiramanu@google.com> Co-authored-by: Varun Rathore <varunrathore@google.com>
1 parent 6ad97cd commit 3f75c4e

File tree

9 files changed

+292
-214
lines changed

9 files changed

+292
-214
lines changed

src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java

Lines changed: 94 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
2122

2223
import com.google.common.collect.ImmutableList;
24+
import com.google.common.collect.ImmutableMap;
2325
import com.google.firebase.internal.NonNull;
2426
import com.google.firebase.internal.Nullable;
25-
2627
import java.math.BigInteger;
2728
import java.nio.charset.StandardCharsets;
2829
import java.security.MessageDigest;
@@ -35,44 +36,46 @@
3536
import java.util.regex.Pattern;
3637
import java.util.regex.PatternSyntaxException;
3738
import java.util.stream.Collectors;
38-
3939
import org.slf4j.Logger;
4040
import org.slf4j.LoggerFactory;
41-
41+
4242
final class ConditionEvaluator {
4343
private static final int MAX_CONDITION_RECURSION_DEPTH = 10;
4444
private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class);
4545
private static final BigInteger MICRO_PERCENT_MODULO = BigInteger.valueOf(100_000_000L);
4646

4747
/**
4848
* Evaluates server conditions and assigns a boolean value to each condition.
49-
*
49+
*
5050
* @param conditions List of conditions which are to be evaluated.
51-
* @param context A map with additional metadata used during evaluation.
51+
* @param context A map with additional metadata used during evaluation.
5252
* @return A map of condition to evaluated value.
5353
*/
5454
@NonNull
5555
Map<String, Boolean> evaluateConditions(
56-
@NonNull List<ServerCondition> conditions,
57-
@Nullable KeysAndValues context) {
56+
@NonNull List<ServerCondition> conditions, @Nullable KeysAndValues context) {
5857
checkNotNull(conditions, "List of conditions must not be null.");
5958
checkArgument(!conditions.isEmpty(), "List of conditions must not be empty.");
60-
KeysAndValues evaluationContext = context != null
61-
? context
62-
: new KeysAndValues.Builder().build();
63-
64-
Map<String, Boolean> evaluatedConditions = conditions.stream()
65-
.collect(Collectors.toMap(
66-
ServerCondition::getName,
67-
condition ->
68-
evaluateCondition(condition.getCondition(), evaluationContext, /* nestingLevel= */0)
69-
));
59+
if (context == null || conditions.isEmpty()) {
60+
return ImmutableMap.of();
61+
}
62+
KeysAndValues evaluationContext =
63+
context != null ? context : new KeysAndValues.Builder().build();
64+
65+
Map<String, Boolean> evaluatedConditions =
66+
conditions.stream()
67+
.collect(
68+
toImmutableMap(
69+
ServerCondition::getName,
70+
condition ->
71+
evaluateCondition(
72+
condition.getCondition(), evaluationContext, /* nestingLevel= */ 0)));
7073

7174
return evaluatedConditions;
7275
}
7376

74-
private boolean evaluateCondition(OneOfCondition condition, KeysAndValues context,
75-
int nestingLevel) {
77+
private boolean evaluateCondition(
78+
OneOfCondition condition, KeysAndValues context, int nestingLevel) {
7679
if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) {
7780
logger.warn("Maximum condition recursion depth exceeded.");
7881
return false;
@@ -95,30 +98,30 @@ private boolean evaluateCondition(OneOfCondition condition, KeysAndValues contex
9598
return false;
9699
}
97100

98-
99-
private boolean evaluateOrCondition(OrCondition condition, KeysAndValues context,
100-
int nestingLevel) {
101+
private boolean evaluateOrCondition(
102+
OrCondition condition, KeysAndValues context, int nestingLevel) {
101103
return condition.getConditions().stream()
102104
.anyMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1));
103105
}
104106

105-
private boolean evaluateAndCondition(AndCondition condition, KeysAndValues context,
106-
int nestingLevel) {
107+
private boolean evaluateAndCondition(
108+
AndCondition condition, KeysAndValues context, int nestingLevel) {
107109
return condition.getConditions().stream()
108110
.allMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1));
109111
}
110112

111-
private boolean evaluateCustomSignalCondition(CustomSignalCondition condition,
112-
KeysAndValues context) {
113+
private boolean evaluateCustomSignalCondition(
114+
CustomSignalCondition condition, KeysAndValues context) {
113115
CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator();
114116
String customSignalKey = condition.getCustomSignalKey();
115-
ImmutableList<String> targetCustomSignalValues = ImmutableList.copyOf(
116-
condition.getTargetCustomSignalValues());
117+
ImmutableList<String> targetCustomSignalValues =
118+
ImmutableList.copyOf(condition.getTargetCustomSignalValues());
117119

118120
if (targetCustomSignalValues.isEmpty()) {
119-
logger.warn(String.format(
120-
"Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s",
121-
customSignalOperator, customSignalKey, targetCustomSignalValues));
121+
logger.warn(
122+
String.format(
123+
"Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s",
124+
customSignalOperator, customSignalKey, targetCustomSignalValues));
122125
return false;
123126
}
124127

@@ -130,64 +133,65 @@ private boolean evaluateCustomSignalCondition(CustomSignalCondition condition,
130133
switch (customSignalOperator) {
131134
// String operations.
132135
case STRING_CONTAINS:
133-
return compareStrings(targetCustomSignalValues, customSignalValue,
136+
return compareStrings(
137+
targetCustomSignalValues,
138+
customSignalValue,
134139
(customSignal, targetSignal) -> customSignal.contains(targetSignal));
135140
case STRING_DOES_NOT_CONTAIN:
136-
return !compareStrings(targetCustomSignalValues, customSignalValue,
141+
return !compareStrings(
142+
targetCustomSignalValues,
143+
customSignalValue,
137144
(customSignal, targetSignal) -> customSignal.contains(targetSignal));
138145
case STRING_EXACTLY_MATCHES:
139-
return compareStrings(targetCustomSignalValues, customSignalValue,
146+
return compareStrings(
147+
targetCustomSignalValues,
148+
customSignalValue,
140149
(customSignal, targetSignal) -> customSignal.equals(targetSignal));
141150
case STRING_CONTAINS_REGEX:
142-
return compareStrings(targetCustomSignalValues, customSignalValue,
151+
return compareStrings(
152+
targetCustomSignalValues,
153+
customSignalValue,
143154
(customSignal, targetSignal) -> compareStringRegex(customSignal, targetSignal));
144155

145156
// Numeric operations.
146157
case NUMERIC_LESS_THAN:
147-
return compareNumbers(targetCustomSignalValues, customSignalValue,
148-
(result) -> result < 0);
158+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result < 0);
149159
case NUMERIC_LESS_EQUAL:
150-
return compareNumbers(targetCustomSignalValues, customSignalValue,
151-
(result) -> result <= 0);
160+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result <= 0);
152161
case NUMERIC_EQUAL:
153-
return compareNumbers(targetCustomSignalValues, customSignalValue,
154-
(result) -> result == 0);
162+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result == 0);
155163
case NUMERIC_NOT_EQUAL:
156-
return compareNumbers(targetCustomSignalValues, customSignalValue,
157-
(result) -> result != 0);
164+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result != 0);
158165
case NUMERIC_GREATER_THAN:
159-
return compareNumbers(targetCustomSignalValues, customSignalValue,
160-
(result) -> result > 0);
166+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result > 0);
161167
case NUMERIC_GREATER_EQUAL:
162-
return compareNumbers(targetCustomSignalValues, customSignalValue,
163-
(result) -> result >= 0);
168+
return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result >= 0);
164169

165170
// Semantic operations.
166171
case SEMANTIC_VERSION_EQUAL:
167-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
168-
(result) -> result == 0);
172+
return compareSemanticVersions(
173+
targetCustomSignalValues, customSignalValue, (result) -> result == 0);
169174
case SEMANTIC_VERSION_GREATER_EQUAL:
170-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
171-
(result) -> result >= 0);
175+
return compareSemanticVersions(
176+
targetCustomSignalValues, customSignalValue, (result) -> result >= 0);
172177
case SEMANTIC_VERSION_GREATER_THAN:
173-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
174-
(result) -> result > 0);
178+
return compareSemanticVersions(
179+
targetCustomSignalValues, customSignalValue, (result) -> result > 0);
175180
case SEMANTIC_VERSION_LESS_EQUAL:
176-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
177-
(result) -> result <= 0);
181+
return compareSemanticVersions(
182+
targetCustomSignalValues, customSignalValue, (result) -> result <= 0);
178183
case SEMANTIC_VERSION_LESS_THAN:
179-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
180-
(result) -> result < 0);
184+
return compareSemanticVersions(
185+
targetCustomSignalValues, customSignalValue, (result) -> result < 0);
181186
case SEMANTIC_VERSION_NOT_EQUAL:
182-
return compareSemanticVersions(targetCustomSignalValues, customSignalValue,
183-
(result) -> result != 0);
187+
return compareSemanticVersions(
188+
targetCustomSignalValues, customSignalValue, (result) -> result != 0);
184189
default:
185190
return false;
186191
}
187192
}
188193

189-
private boolean evaluatePercentCondition(PercentCondition condition,
190-
KeysAndValues context) {
194+
private boolean evaluatePercentCondition(PercentCondition condition, KeysAndValues context) {
191195
if (!context.containsKey("randomizationId")) {
192196
logger.warn("Percentage operation must not be performed without randomizationId");
193197
return false;
@@ -197,18 +201,16 @@ private boolean evaluatePercentCondition(PercentCondition condition,
197201

198202
// The micro-percent interval to be used with the BETWEEN operator.
199203
MicroPercentRange microPercentRange = condition.getMicroPercentRange();
200-
int microPercentUpperBound = microPercentRange != null
201-
? microPercentRange.getMicroPercentUpperBound()
202-
: 0;
203-
int microPercentLowerBound = microPercentRange != null
204-
? microPercentRange.getMicroPercentLowerBound()
205-
: 0;
204+
int microPercentUpperBound =
205+
microPercentRange != null ? microPercentRange.getMicroPercentUpperBound() : 0;
206+
int microPercentLowerBound =
207+
microPercentRange != null ? microPercentRange.getMicroPercentLowerBound() : 0;
206208
// The limit of percentiles to target in micro-percents when using the
207209
// LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0
208210
// and 100000000].
209211
int microPercent = condition.getMicroPercent();
210-
BigInteger microPercentile = getMicroPercentile(condition.getSeed(),
211-
context.get("randomizationId"));
212+
BigInteger microPercentile =
213+
getMicroPercentile(condition.getSeed(), context.get("randomizationId"));
212214
switch (operator) {
213215
case LESS_OR_EQUAL:
214216
return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0;
@@ -246,10 +248,12 @@ private BigInteger hashSeededRandomizationId(String seededRandomizationId) {
246248
}
247249
}
248250

249-
private boolean compareStrings(ImmutableList<String> targetValues, String customSignal,
250-
BiPredicate<String, String> compareFunction) {
251-
return targetValues.stream().anyMatch(targetValue ->
252-
compareFunction.test(customSignal, targetValue));
251+
private boolean compareStrings(
252+
ImmutableList<String> targetValues,
253+
String customSignal,
254+
BiPredicate<String, String> compareFunction) {
255+
return targetValues.stream()
256+
.anyMatch(targetValue -> compareFunction.test(customSignal, targetValue));
253257
}
254258

255259
private boolean compareStringRegex(String customSignal, String targetSignal) {
@@ -260,12 +264,13 @@ private boolean compareStringRegex(String customSignal, String targetSignal) {
260264
}
261265
}
262266

263-
private boolean compareNumbers(ImmutableList<String> targetValues, String customSignal,
264-
IntPredicate compareFunction) {
267+
private boolean compareNumbers(
268+
ImmutableList<String> targetValues, String customSignal, IntPredicate compareFunction) {
265269
if (targetValues.size() != 1) {
266-
logger.warn(String.format(
267-
"Target values must contain 1 element for numeric operations. Target Value: %s",
268-
targetValues));
270+
logger.warn(
271+
String.format(
272+
"Target values must contain 1 element for numeric operations. Target Value: %s",
273+
targetValues));
269274
return false;
270275
}
271276

@@ -275,23 +280,22 @@ private boolean compareNumbers(ImmutableList<String> targetValues, String custom
275280
int comparisonResult = Double.compare(customSignalDouble, targetValue);
276281
return compareFunction.test(comparisonResult);
277282
} catch (NumberFormatException e) {
278-
logger.warn("Error parsing numeric values: customSignal=%s, targetValue=%s",
283+
logger.warn(
284+
"Error parsing numeric values: customSignal=%s, targetValue=%s",
279285
customSignal, targetValues.get(0), e);
280286
return false;
281287
}
282288
}
283289

284-
private boolean compareSemanticVersions(ImmutableList<String> targetValues,
285-
String customSignal,
286-
IntPredicate compareFunction) {
290+
private boolean compareSemanticVersions(
291+
ImmutableList<String> targetValues, String customSignal, IntPredicate compareFunction) {
287292
if (targetValues.size() != 1) {
288293
logger.warn(String.format("Target values must contain 1 element for semantic operation."));
289294
return false;
290295
}
291296

292297
String targetValueString = targetValues.get(0);
293-
if (!validateSemanticVersion(targetValueString)
294-
|| !validateSemanticVersion(customSignal)) {
298+
if (!validateSemanticVersion(targetValueString) || !validateSemanticVersion(customSignal)) {
295299
return false;
296300
}
297301

@@ -300,7 +304,8 @@ private boolean compareSemanticVersions(ImmutableList<String> targetValues,
300304

301305
int maxLength = 5;
302306
if (targetVersion.size() > maxLength || customSignalVersion.size() > maxLength) {
303-
logger.warn("Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s",
307+
logger.warn(
308+
"Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s",
304309
maxLength, targetValueString, customSignal);
305310
return false;
306311
}
@@ -316,8 +321,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
316321

317322
for (int i = 0; i < maxLength; i++) {
318323
// Default to 0 if segment is missing
319-
int v1 = i < version1Size ? version1.get(i) : 0;
320-
int v2 = i < version2Size ? version2.get(i) : 0;
324+
int v1 = i < version1Size ? version1.get(i) : 0;
325+
int v2 = i < version2Size ? version2.get(i) : 0;
321326

322327
int comparison = Integer.compare(v1, v2);
323328
if (comparison != 0) {
@@ -330,8 +335,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
330335

331336
private List<Integer> parseSemanticVersion(String versionString) {
332337
return Arrays.stream(versionString.split("\\."))
333-
.map(Integer::parseInt)
334-
.collect(Collectors.toList());
338+
.map(Integer::parseInt)
339+
.collect(Collectors.toList());
335340
}
336341

337342
private boolean validateSemanticVersion(String version) {

src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@
2020
import com.google.api.core.ApiFutures;
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.firebase.ErrorCode;
23+
import com.google.firebase.internal.Nullable;
2324
import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse;
24-
import com.google.gson.Gson;
25-
import com.google.gson.GsonBuilder;
2625

2726
import java.util.HashMap;
2827
import java.util.Map;
@@ -43,7 +42,7 @@ public static class Builder implements ServerTemplate.Builder {
4342
private KeysAndValues defaultConfig;
4443
private String cachedTemplate;
4544
private FirebaseRemoteConfigClient client;
46-
45+
4746
Builder(FirebaseRemoteConfigClient remoteConfigClient) {
4847
this.client = remoteConfigClient;
4948
}
@@ -78,7 +77,8 @@ private ServerTemplateImpl(Builder builder) {
7877
}
7978

8079
@Override
81-
public ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException {
80+
public ServerConfig evaluate(@Nullable KeysAndValues context)
81+
throws FirebaseRemoteConfigException {
8282
if (this.cache == null) {
8383
throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION,
8484
"No Remote Config Server template in cache. Call load() before calling evaluate().");
@@ -103,8 +103,7 @@ public ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigE
103103

104104
@Override
105105
public ServerConfig evaluate() throws FirebaseRemoteConfigException {
106-
KeysAndValues context = new KeysAndValues.Builder().build();
107-
return evaluate(context);
106+
return evaluate(null);
108107
}
109108

110109
@Override
@@ -126,8 +125,7 @@ public String getCachedTemplate() {
126125

127126
@Override
128127
public String toJson() {
129-
Gson gson = new GsonBuilder().setPrettyPrinting().create();
130-
return gson.toJson(this.cache);
128+
return this.cache.toJSON();
131129
}
132130

133131
private void mergeDerivedConfigValues(ImmutableMap<String, Boolean> evaluatedCondition,

0 commit comments

Comments
 (0)