Skip to content

Commit 5eaa9ef

Browse files
rathovarun1032Varun Rathore
andauthored
Implementation for Fetching and Caching Server Side Remote Config (#1107)009
* Implementation for Fetching and Caching Server Side Remote Config * implementation of fetch , cache and load of template --------- Co-authored-by: Varun Rathore <varunrathore@google.com>
1 parent 4f73761 commit 5eaa9ef

18 files changed

+1582
-3
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
/*
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.google.firebase.remoteconfig;
19+
20+
import static com.google.common.base.Preconditions.checkArgument;
21+
import static com.google.common.base.Preconditions.checkNotNull;
22+
23+
import com.google.common.collect.ImmutableList;
24+
import com.google.firebase.internal.NonNull;
25+
import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.AndConditionResponse;
26+
import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse;
27+
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.stream.Collectors;
31+
32+
final class AndCondition {
33+
private final ImmutableList<OneOfCondition> conditions;
34+
35+
AndCondition(@NonNull List<OneOfCondition> conditions) {
36+
checkNotNull(conditions, "List of conditions for AND operation must not be null.");
37+
checkArgument(!conditions.isEmpty(),
38+
"List of conditions for AND operation must not be empty.");
39+
this.conditions = ImmutableList.copyOf(conditions);
40+
}
41+
42+
AndCondition(AndConditionResponse andConditionResponse) {
43+
List<OneOfConditionResponse> conditionList = andConditionResponse.getConditions();
44+
checkNotNull(conditionList, "List of conditions for AND operation must not be null.");
45+
checkArgument(!conditionList.isEmpty(),
46+
"List of conditions for AND operation must not be empty");
47+
this.conditions = conditionList.stream()
48+
.map(OneOfCondition::new)
49+
.collect(ImmutableList.toImmutableList());
50+
}
51+
52+
@NonNull
53+
List<OneOfCondition> getConditions() {
54+
return new ArrayList<>(conditions);
55+
}
56+
57+
AndConditionResponse toAndConditionResponse() {
58+
return new AndConditionResponse()
59+
.setConditions(this.conditions.stream()
60+
.map(OneOfCondition::toOneOfConditionResponse)
61+
.collect(Collectors.toList()));
62+
}
63+
}
64+

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,73 @@ protected Template execute() throws FirebaseRemoteConfigException {
101101
};
102102
}
103103

104+
/**
105+
* Alternative to {@link #getServerTemplate} where developers can initialize with a pre-cached
106+
* template or config.
107+
*/
108+
public ServerTemplateImpl.Builder serverTemplateBuilder() {
109+
return new ServerTemplateImpl.Builder(this.remoteConfigClient);
110+
}
111+
112+
/**
113+
* Initializes a template instance and loads the latest template data.
114+
*
115+
* @param defaultConfig Default parameter values to use if a getter references a parameter not
116+
* found in the template.
117+
* @return A {@link Template} instance with the latest template data.
118+
*/
119+
public ServerTemplate getServerTemplate(KeysAndValues defaultConfig)
120+
throws FirebaseRemoteConfigException {
121+
return getServerTemplateOp(defaultConfig).call();
122+
}
123+
124+
/**
125+
* Initializes a template instance without any defaults and loads the latest template data.
126+
*
127+
* @return A {@link Template} instance with the latest template data.
128+
*/
129+
public ServerTemplate getServerTemplate() throws FirebaseRemoteConfigException {
130+
return getServerTemplate(null);
131+
}
132+
133+
/**
134+
* Initializes a template instance and asynchronously loads the latest template data.
135+
*
136+
* @param defaultConfig Default parameter values to use if a getter references a parameter not
137+
* found in the template.
138+
* @return A {@link Template} instance with the latest template data.
139+
*/
140+
public ApiFuture<ServerTemplate> getServerTemplateAsync(KeysAndValues defaultConfig) {
141+
return getServerTemplateOp(defaultConfig).callAsync(app);
142+
}
143+
144+
/**
145+
* Initializes a template instance without any defaults and asynchronously loads the latest
146+
* template data.
147+
*
148+
* @return A {@link Template} instance with the latest template data.
149+
*/
150+
public ApiFuture<ServerTemplate> getServerTemplateAsync() {
151+
return getServerTemplateAsync(null);
152+
}
153+
154+
private CallableOperation<ServerTemplate, FirebaseRemoteConfigException> getServerTemplateOp(
155+
KeysAndValues defaultConfig) {
156+
return new CallableOperation<ServerTemplate, FirebaseRemoteConfigException>() {
157+
@Override
158+
protected ServerTemplate execute() throws FirebaseRemoteConfigException {
159+
String serverTemplateData = remoteConfigClient.getServerTemplate();
160+
ServerTemplate template =
161+
serverTemplateBuilder()
162+
.defaultConfig(defaultConfig)
163+
.cachedTemplate(serverTemplateData)
164+
.build();
165+
166+
return template;
167+
}
168+
};
169+
}
170+
104171
/**
105172
* Gets the requested version of the of the Remote Config template.
106173
*
@@ -413,3 +480,4 @@ public void destroy() {
413480
}
414481
}
415482
}
483+

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ Template publishTemplate(Template template, boolean validateOnly,
4040

4141
ListVersionsResponse listVersions(
4242
ListVersionsOptions options) throws FirebaseRemoteConfigException;
43+
44+
String getServerTemplate() throws FirebaseRemoteConfigException;
4345
}
46+

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.google.firebase.internal.NonNull;
3939
import com.google.firebase.internal.SdkUtils;
4040
import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse;
41+
import com.google.firebase.remoteconfig.internal.ServerTemplateResponse;
4142
import com.google.firebase.remoteconfig.internal.TemplateResponse;
4243

4344
import java.io.IOException;
@@ -51,6 +52,9 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient
5152

5253
private static final String REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig";
5354

55+
private static final String SERVER_REMOTE_CONFIG_URL =
56+
"https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/firebase-server/serverRemoteConfig";
57+
5458
private static final Map<String, String> COMMON_HEADERS =
5559
ImmutableMap.of(
5660
"X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion(),
@@ -62,13 +66,15 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient
6266
);
6367

6468
private final String remoteConfigUrl;
69+
private final String serverRemoteConfigUrl;
6570
private final HttpRequestFactory requestFactory;
6671
private final JsonFactory jsonFactory;
6772
private final ErrorHandlingHttpClient<FirebaseRemoteConfigException> httpClient;
6873

6974
private FirebaseRemoteConfigClientImpl(Builder builder) {
7075
checkArgument(!Strings.isNullOrEmpty(builder.projectId));
7176
this.remoteConfigUrl = String.format(REMOTE_CONFIG_URL, builder.projectId);
77+
this.serverRemoteConfigUrl = String.format(SERVER_REMOTE_CONFIG_URL, builder.projectId);
7278
this.requestFactory = checkNotNull(builder.requestFactory);
7379
this.jsonFactory = checkNotNull(builder.jsonFactory);
7480
HttpResponseInterceptor responseInterceptor = builder.responseInterceptor;
@@ -82,6 +88,11 @@ String getRemoteConfigUrl() {
8288
return remoteConfigUrl;
8389
}
8490

91+
@VisibleForTesting
92+
String getServerRemoteConfigUrl() {
93+
return serverRemoteConfigUrl;
94+
}
95+
8596
@VisibleForTesting
8697
HttpRequestFactory getRequestFactory() {
8798
return requestFactory;
@@ -102,6 +113,18 @@ public Template getTemplate() throws FirebaseRemoteConfigException {
102113
return template.setETag(getETag(response));
103114
}
104115

116+
@Override
117+
public String getServerTemplate() throws FirebaseRemoteConfigException {
118+
HttpRequestInfo request =
119+
HttpRequestInfo.buildGetRequest(serverRemoteConfigUrl).addAllHeaders(COMMON_HEADERS);
120+
IncomingHttpResponse response = httpClient.send(request);
121+
ServerTemplateResponse templateResponse = httpClient.parse(response,
122+
ServerTemplateResponse.class);
123+
ServerTemplateData serverTemplateData = new ServerTemplateData(templateResponse);
124+
serverTemplateData.setETag(getETag(response));
125+
return serverTemplateData.toJSON();
126+
}
127+
105128
@Override
106129
public Template getTemplateAtVersion(
107130
@NonNull String versionNumber) throws FirebaseRemoteConfigException {
@@ -267,3 +290,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) {
267290
}
268291
}
269292
}
293+
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
/*
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.google.firebase.remoteconfig;
19+
20+
import static com.google.common.base.Preconditions.checkArgument;
21+
22+
import com.google.common.base.Strings;
23+
import com.google.common.collect.ImmutableMap;
24+
import com.google.firebase.internal.NonNull;
25+
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
29+
/**
30+
* Represents data stored in context passed to server-side Remote Config.
31+
*/
32+
public class KeysAndValues {
33+
final ImmutableMap<String, String> keysAndValues;
34+
35+
private KeysAndValues(@NonNull Builder builder) {
36+
keysAndValues = ImmutableMap.<String, String>builder().putAll(builder.keysAndValues).build();
37+
}
38+
39+
/**
40+
* Checks whether a key is present in the context.
41+
*
42+
* @param key The key for data stored in context.
43+
* @return Boolean representing whether the key passed is present in context.
44+
*/
45+
public boolean containsKey(String key) {
46+
return keysAndValues.containsKey(key);
47+
}
48+
49+
/**
50+
* Gets the value of the data stored in context.
51+
*
52+
* @param key The key for data stored in context.
53+
* @return Value assigned to the key in context.
54+
*/
55+
public String get(String key) {
56+
return keysAndValues.get(key);
57+
}
58+
59+
/**
60+
* Builder class for KeysAndValues using which values will be assigned to
61+
* private variables.
62+
*/
63+
public static class Builder {
64+
// Holds the converted pairs of custom keys and values.
65+
private final Map<String, String> keysAndValues = new HashMap<>();
66+
67+
/**
68+
* Adds a context data with string value.
69+
*
70+
* @param key Identifies the value in context.
71+
* @param value Value assigned to the context.
72+
* @return Reference to class itself so that more data can be added.
73+
*/
74+
@NonNull
75+
public Builder put(@NonNull String key, @NonNull String value) {
76+
checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty.");
77+
checkArgument(!Strings.isNullOrEmpty(value), "Context key must not be null or empty.");
78+
keysAndValues.put(key, value);
79+
return this;
80+
}
81+
82+
/**
83+
* Adds a context data with boolean value.
84+
*
85+
* @param key Identifies the value in context.
86+
* @param value Value assigned to the context.
87+
* @return Reference to class itself so that more data can be added.
88+
*/
89+
@NonNull
90+
public Builder put(@NonNull String key, boolean value) {
91+
checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty.");
92+
keysAndValues.put(key, Boolean.toString(value));
93+
return this;
94+
}
95+
96+
/**
97+
* Adds a context data with double value.
98+
*
99+
* @param key Identifies the value in context.
100+
* @param value Value assigned to the context.
101+
* @return Reference to class itself so that more data can be added.
102+
*/
103+
@NonNull
104+
public Builder put(@NonNull String key, double value) {
105+
checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty.");
106+
keysAndValues.put(key, Double.toString(value));
107+
return this;
108+
}
109+
110+
/**
111+
* Adds a context data with long value.
112+
*
113+
* @param key Identifies the value in context.
114+
* @param value Value assigned to the context.
115+
* @return Reference to class itself so that more data can be added.
116+
*/
117+
@NonNull
118+
public Builder put(@NonNull String key, long value) {
119+
checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty.");
120+
keysAndValues.put(key, Long.toString(value));
121+
return this;
122+
}
123+
124+
/**
125+
* Creates an instance of KeysAndValues with the values assigned through
126+
* builder.
127+
*
128+
* @return instance of KeysAndValues
129+
*/
130+
@NonNull
131+
public KeysAndValues build() {
132+
return new KeysAndValues(this);
133+
}
134+
}
135+
}
136+

0 commit comments

Comments
 (0)