Skip to content

Commit 1a3f036

Browse files
Add Remote Config Pubish, Validate, and GetTemplateAtVersion operations (#496)
* Add Pubish, Validate, and GetTemplateAtVersion operations * Clean up tests * Move PublishOptions to parent package
1 parent daf8b2c commit 1a3f036

File tree

8 files changed

+1000
-60
lines changed

8 files changed

+1000
-60
lines changed

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

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.firebase.ImplFirebaseTrampolines;
2525
import com.google.firebase.internal.CallableOperation;
2626
import com.google.firebase.internal.FirebaseService;
27+
import com.google.firebase.internal.NonNull;
2728

2829
/**
2930
* This class is the entry point for all server-side Firebase Remote Config actions.
@@ -100,6 +101,160 @@ protected Template execute() throws FirebaseRemoteConfigException {
100101
};
101102
}
102103

104+
/**
105+
* Gets the requested version of the of the Remote Config template.
106+
*
107+
* @param versionNumber The version number of the Remote Config template to look up.
108+
* @return A {@link Template}.
109+
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
110+
*/
111+
public Template getTemplateAtVersion(
112+
@NonNull String versionNumber) throws FirebaseRemoteConfigException {
113+
return getTemplateAtVersionOp(versionNumber).call();
114+
}
115+
116+
/**
117+
* Gets the requested version of the of the Remote Config template.
118+
*
119+
* @param versionNumber The version number of the Remote Config template to look up.
120+
* @return A {@link Template}.
121+
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
122+
*/
123+
public Template getTemplateAtVersion(long versionNumber) throws FirebaseRemoteConfigException {
124+
String versionNumberString = String.valueOf(versionNumber);
125+
return getTemplateAtVersionOp(versionNumberString).call();
126+
}
127+
128+
/**
129+
* Similar to {@link #getTemplateAtVersion(String versionNumber)} but performs the operation
130+
* asynchronously.
131+
*
132+
* @param versionNumber The version number of the Remote Config template to look up.
133+
* @return An {@code ApiFuture} that completes with a {@link Template} when
134+
* the requested template is available.
135+
*/
136+
public ApiFuture<Template> getTemplateAtVersionAsync(@NonNull String versionNumber) {
137+
return getTemplateAtVersionOp(versionNumber).callAsync(app);
138+
}
139+
140+
/**
141+
* Similar to {@link #getTemplateAtVersion(long versionNumber)} but performs the operation
142+
* asynchronously.
143+
*
144+
* @param versionNumber The version number of the Remote Config template to look up.
145+
* @return An {@code ApiFuture} that completes with a {@link Template} when
146+
* the requested template is available.
147+
*/
148+
public ApiFuture<Template> getTemplateAtVersionAsync(long versionNumber) {
149+
String versionNumberString = String.valueOf(versionNumber);
150+
return getTemplateAtVersionOp(versionNumberString).callAsync(app);
151+
}
152+
153+
private CallableOperation<Template, FirebaseRemoteConfigException> getTemplateAtVersionOp(
154+
final String versionNumber) {
155+
final FirebaseRemoteConfigClient remoteConfigClient = getRemoteConfigClient();
156+
return new CallableOperation<Template, FirebaseRemoteConfigException>() {
157+
@Override
158+
protected Template execute() throws FirebaseRemoteConfigException {
159+
return remoteConfigClient.getTemplateAtVersion(versionNumber);
160+
}
161+
};
162+
}
163+
164+
/**
165+
* Publishes a Remote Config template.
166+
*
167+
* @param template The Remote Config template to be published.
168+
* @return The published {@link Template}.
169+
* @throws FirebaseRemoteConfigException If an error occurs while publishing the template.
170+
*/
171+
public Template publishTemplate(@NonNull Template template) throws FirebaseRemoteConfigException {
172+
return publishTemplateOp(template).call();
173+
}
174+
175+
/**
176+
* Similar to {@link #publishTemplate(Template template)} but performs the operation
177+
* asynchronously.
178+
*
179+
* @param template The Remote Config template to be published.
180+
* @return An {@code ApiFuture} that completes with a {@link Template} when
181+
* the provided template is published.
182+
*/
183+
public ApiFuture<Template> publishTemplateAsync(@NonNull Template template) {
184+
return publishTemplateOp(template).callAsync(app);
185+
}
186+
187+
/**
188+
* Validates a Remote Config template.
189+
*
190+
* @param template The Remote Config template to be validated.
191+
* @return The validated {@link Template}.
192+
* @throws FirebaseRemoteConfigException If an error occurs while validating the template.
193+
*/
194+
public Template validateTemplate(
195+
@NonNull Template template) throws FirebaseRemoteConfigException {
196+
return publishTemplateOp(template, new PublishOptions().setValidateOnly(true)).call();
197+
}
198+
199+
/**
200+
* Similar to {@link #validateTemplate(Template template)} but performs the operation
201+
* asynchronously.
202+
*
203+
* @param template The Remote Config template to be validated.
204+
* @return An {@code ApiFuture} that completes with a {@link Template} when
205+
* the provided template is validated.
206+
*/
207+
public ApiFuture<Template> validateTemplateAsync(@NonNull Template template) {
208+
return publishTemplateOp(template, new PublishOptions().setValidateOnly(true)).callAsync(app);
209+
}
210+
211+
/**
212+
* Force publishes a Remote Config template.
213+
*
214+
* <p>This method forces the Remote Config template to be updated and circumvent the ETag.
215+
* This approach is not recommended because it risks causing the loss of updates to your
216+
* Remote Config template if multiple clients are updating the Remote Config template.
217+
* See <a href="https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates">
218+
* ETag usage and forced updates</a>.
219+
*
220+
* @param template The Remote Config template to be forcefully published.
221+
* @return The published {@link Template}.
222+
* @throws FirebaseRemoteConfigException If an error occurs while publishing the template.
223+
*/
224+
public Template forcePublishTemplate(
225+
@NonNull Template template) throws FirebaseRemoteConfigException {
226+
return publishTemplateOp(template, new PublishOptions().setForcePublish(true)).call();
227+
}
228+
229+
/**
230+
* Similar to {@link #forcePublishTemplate(Template template)} but performs the operation
231+
* asynchronously.
232+
*
233+
* @param template The Remote Config template to be forcefully published.
234+
* @return An {@code ApiFuture} that completes with a {@link Template} when
235+
* the provided template is published.
236+
*/
237+
public ApiFuture<Template> forcePublishTemplateAsync(@NonNull Template template) {
238+
return publishTemplateOp(template, new PublishOptions().setForcePublish(true)).callAsync(app);
239+
}
240+
241+
private CallableOperation<Template, FirebaseRemoteConfigException> publishTemplateOp(
242+
final Template template) {
243+
return publishTemplateOp(template, new PublishOptions());
244+
}
245+
246+
private CallableOperation<Template, FirebaseRemoteConfigException> publishTemplateOp(
247+
final Template template, final PublishOptions options) {
248+
final FirebaseRemoteConfigClient remoteConfigClient = getRemoteConfigClient();
249+
return new CallableOperation<Template, FirebaseRemoteConfigException>() {
250+
@Override
251+
protected Template execute() throws FirebaseRemoteConfigException {
252+
return remoteConfigClient
253+
.publishTemplate(template, options.isValidateOnly(), options.isForcePublish());
254+
}
255+
};
256+
}
257+
103258
@VisibleForTesting
104259
FirebaseRemoteConfigClient getRemoteConfigClient() {
105260
return remoteConfigClient;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ interface FirebaseRemoteConfigClient {
2828
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
2929
*/
3030
Template getTemplate() throws FirebaseRemoteConfigException;
31+
32+
Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException;
33+
34+
Template publishTemplate(Template template, boolean validateOnly,
35+
boolean forcePublish) throws FirebaseRemoteConfigException;
3136
}

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.google.api.client.http.HttpRequestFactory;
2424
import com.google.api.client.http.HttpResponseInterceptor;
25+
import com.google.api.client.http.json.JsonHttpContent;
2526
import com.google.api.client.json.JsonFactory;
2627
import com.google.common.annotations.VisibleForTesting;
2728
import com.google.common.base.Strings;
@@ -34,6 +35,7 @@
3435
import com.google.firebase.internal.ApiClientUtils;
3536
import com.google.firebase.internal.ErrorHandlingHttpClient;
3637
import com.google.firebase.internal.HttpRequestInfo;
38+
import com.google.firebase.internal.NonNull;
3739
import com.google.firebase.internal.SdkUtils;
3840
import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse;
3941
import com.google.firebase.remoteconfig.internal.TemplateResponse;
@@ -100,6 +102,42 @@ public Template getTemplate() throws FirebaseRemoteConfigException {
100102
return template.setETag(getETag(response));
101103
}
102104

105+
@Override
106+
public Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException {
107+
checkArgument(isValidVersionNumber(versionNumber),
108+
"Version number must be a non-empty string in int64 format.");
109+
HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl)
110+
.addAllHeaders(COMMON_HEADERS)
111+
.addParameter("versionNumber", versionNumber);
112+
IncomingHttpResponse response = httpClient.send(request);
113+
TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class);
114+
Template template = new Template(templateResponse);
115+
return template.setETag(getETag(response));
116+
}
117+
118+
@Override
119+
public Template publishTemplate(@NonNull Template template, boolean validateOnly,
120+
boolean forcePublish) throws FirebaseRemoteConfigException {
121+
checkArgument(template != null, "Template must not be null.");
122+
HttpRequestInfo request = HttpRequestInfo.buildRequest("PUT", remoteConfigUrl,
123+
new JsonHttpContent(jsonFactory, template.toTemplateResponse()))
124+
.addAllHeaders(COMMON_HEADERS)
125+
.addHeader("If-Match", forcePublish ? "*" : template.getETag());
126+
if (validateOnly) {
127+
request.addParameter("validateOnly", true);
128+
}
129+
IncomingHttpResponse response = httpClient.send(request);
130+
TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class);
131+
Template publishedTemplate = new Template(templateResponse);
132+
if (validateOnly) {
133+
// validating a template returns an etag with the suffix -0 means that the provided template
134+
// was successfully validated. We set the etag back to the original etag of the template
135+
// to allow subsequent operations.
136+
return publishedTemplate.setETag(template.getETag());
137+
}
138+
return publishedTemplate.setETag(getETag(response));
139+
}
140+
103141
private String getETag(IncomingHttpResponse response) {
104142
List<String> etagList = (List<String>) response.getHeaders().get("etag");
105143
checkState(etagList != null && !etagList.isEmpty(),
@@ -112,6 +150,10 @@ private String getETag(IncomingHttpResponse response) {
112150
return etag;
113151
}
114152

153+
private boolean isValidVersionNumber(String versionNumber) {
154+
return !Strings.isNullOrEmpty(versionNumber) && versionNumber.matches("^\\d+$");
155+
}
156+
115157
static FirebaseRemoteConfigClientImpl fromApp(FirebaseApp app) {
116158
String projectId = ImplFirebaseTrampolines.getProjectId(app);
117159
checkArgument(!Strings.isNullOrEmpty(projectId),
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.google.firebase.remoteconfig;
2+
3+
/**
4+
* An internal class for publish template options.
5+
*/
6+
final class PublishOptions {
7+
8+
private boolean validateOnly;
9+
private boolean forcePublish;
10+
11+
PublishOptions() {
12+
validateOnly = false;
13+
forcePublish = false;
14+
}
15+
16+
public PublishOptions setForcePublish(boolean forcePublish) {
17+
this.forcePublish = forcePublish;
18+
return this;
19+
}
20+
21+
public PublishOptions setValidateOnly(boolean validateOnly) {
22+
this.validateOnly = validateOnly;
23+
return this;
24+
}
25+
26+
public boolean isForcePublish() {
27+
return forcePublish;
28+
}
29+
30+
public boolean isValidateOnly() {
31+
return validateOnly;
32+
}
33+
}

0 commit comments

Comments
 (0)