Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.internal.CallableOperation;
import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.NonNull;

/**
* This class is the entry point for all server-side Firebase Remote Config actions.
Expand Down Expand Up @@ -100,6 +101,154 @@ protected Template execute() throws FirebaseRemoteConfigException {
};
}

/**
* Gets the requested version of the of the Remote Config template.
*
* @param versionNumber The version number of the Remote Config template to look up.
* @return A {@link Template}.
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
*/
public Template getTemplateAtVersion(
@NonNull String versionNumber) throws FirebaseRemoteConfigException {
return getTemplateAtVersionOp(versionNumber).call();
}

/**
* Gets the requested version of the of the Remote Config template.
*
* @param versionNumber The version number of the Remote Config template to look up.
* @return A {@link Template}.
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
*/
public Template getTemplateAtVersion(long versionNumber) throws FirebaseRemoteConfigException {
String versionNumberString = String.valueOf(versionNumber);
return getTemplateAtVersionOp(versionNumberString).call();
}

/**
* Similar to {@link #getTemplateAtVersion(String versionNumber)} but performs the operation
* asynchronously.
*
* @param versionNumber The version number of the Remote Config template to look up.
* @return An {@code ApiFuture} that completes with a {@link Template} when
* the requested template is available.
*/
public ApiFuture<Template> getTemplateAtVersionAsync(@NonNull String versionNumber) {
return getTemplateAtVersionOp(versionNumber).callAsync(app);
}

/**
* Similar to {@link #getTemplateAtVersion(long versionNumber)} but performs the operation
* asynchronously.
*
* @param versionNumber The version number of the Remote Config template to look up.
* @return An {@code ApiFuture} that completes with a {@link Template} when
* the requested template is available.
*/
public ApiFuture<Template> getTemplateAtVersionAsync(long versionNumber) {
String versionNumberString = String.valueOf(versionNumber);
return getTemplateAtVersionOp(versionNumberString).callAsync(app);
}

private CallableOperation<Template, FirebaseRemoteConfigException> getTemplateAtVersionOp(
final String versionNumber) {
final FirebaseRemoteConfigClient remoteConfigClient = getRemoteConfigClient();
return new CallableOperation<Template, FirebaseRemoteConfigException>() {
@Override
protected Template execute() throws FirebaseRemoteConfigException {
return remoteConfigClient.getTemplateAtVersion(versionNumber);
}
};
}

/**
* Publishes a Remote Config template.
*
* @param template The Remote Config template to be published.
* @return The published {@link Template}.
* @throws FirebaseRemoteConfigException If an error occurs while publishing the template.
*/
public Template publishTemplate(@NonNull Template template) throws FirebaseRemoteConfigException {
return publishTemplateOp(template, false, false).call();
}

/**
* Similar to {@link #publishTemplate(Template template)} but performs the operation
* asynchronously.
*
* @param template The Remote Config template to be published.
* @return An {@code ApiFuture} that completes with a {@link Template} when
* the provided template is published.
*/
public ApiFuture<Template> publishTemplateAsync(@NonNull Template template) {
return publishTemplateOp(template, false, false).callAsync(app);
}

/**
* Validates a Remote Config template.
*
* @param template The Remote Config template to be validated.
* @return The validated {@link Template}.
* @throws FirebaseRemoteConfigException If an error occurs while validating the template.
*/
public Template validateTemplate(
@NonNull Template template) throws FirebaseRemoteConfigException {
return publishTemplateOp(template, true, false).call();
}

/**
* Similar to {@link #validateTemplate(Template template)} but performs the operation
* asynchronously.
*
* @param template The Remote Config template to be validated.
* @return An {@code ApiFuture} that completes with a {@link Template} when
* the provided template is validated.
*/
public ApiFuture<Template> validateTemplateAsync(@NonNull Template template) {
return publishTemplateOp(template, true, false).callAsync(app);
}

/**
* Force publishes a Remote Config template.
*
* <p>This method forces the Remote Config template to be updated and circumvent the ETag.
* This approach is not recommended because it risks causing the loss of updates to your
* Remote Config template if multiple clients are updating the Remote Config template.
* See <a href="https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates">
* ETag usage and forced updates</a>.
*
* @param template The Remote Config template to be forcefully published.
* @return The published {@link Template}.
* @throws FirebaseRemoteConfigException If an error occurs while publishing the template.
*/
public Template forcePublishTemplate(
@NonNull Template template) throws FirebaseRemoteConfigException {
return publishTemplateOp(template, false, true).call();
}

/**
* Similar to {@link #forcePublishTemplate(Template template)} but performs the operation
* asynchronously.
*
* @param template The Remote Config template to be forcefully published.
* @return An {@code ApiFuture} that completes with a {@link Template} when
* the provided template is published.
*/
public ApiFuture<Template> forcePublishTemplateAsync(@NonNull Template template) {
return publishTemplateOp(template, false, true).callAsync(app);
}

private CallableOperation<Template, FirebaseRemoteConfigException> publishTemplateOp(
final Template template, final boolean validateOnly, final boolean forcePublish) {
final FirebaseRemoteConfigClient remoteConfigClient = getRemoteConfigClient();
return new CallableOperation<Template, FirebaseRemoteConfigException>() {
@Override
protected Template execute() throws FirebaseRemoteConfigException {
return remoteConfigClient.publishTemplate(template, validateOnly, forcePublish);
}
};
}

@VisibleForTesting
FirebaseRemoteConfigClient getRemoteConfigClient() {
return remoteConfigClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ interface FirebaseRemoteConfigClient {
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
*/
Template getTemplate() throws FirebaseRemoteConfigException;

Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException;

Template publishTemplate(Template template, boolean validateOnly,
boolean forcePublish) throws FirebaseRemoteConfigException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.JsonFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
Expand All @@ -34,6 +35,7 @@
import com.google.firebase.internal.ApiClientUtils;
import com.google.firebase.internal.ErrorHandlingHttpClient;
import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.SdkUtils;
import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse;
import com.google.firebase.remoteconfig.internal.TemplateResponse;
Expand Down Expand Up @@ -100,6 +102,42 @@ public Template getTemplate() throws FirebaseRemoteConfigException {
return template.setETag(getETag(response));
}

@Override
public Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException {
checkArgument(isValidVersionNumber(versionNumber),
"Version number must be a non-empty string in int64 format.");
HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl)
.addAllHeaders(COMMON_HEADERS)
.addParameter("versionNumber", versionNumber);
IncomingHttpResponse response = httpClient.send(request);
TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class);
Template template = new Template(templateResponse);
return template.setETag(getETag(response));
}

@Override
public Template publishTemplate(@NonNull Template template, boolean validateOnly,
boolean forcePublish) throws FirebaseRemoteConfigException {
checkArgument(template != null, "Template must not be null.");
HttpRequestInfo request = HttpRequestInfo.buildRequest("PUT", remoteConfigUrl,
new JsonHttpContent(jsonFactory, template.toTemplateResponse()))
.addAllHeaders(COMMON_HEADERS)
.addHeader("If-Match", forcePublish ? "*" : template.getETag());
if (validateOnly) {
request.addParameter("validateOnly", true);
}
IncomingHttpResponse response = httpClient.send(request);
TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class);
Template publishedTemplate = new Template(templateResponse);
if (validateOnly) {
// validating a template returns an etag with the suffix -0 means that the provided template
// was successfully validated. We set the etag back to the original etag of the template
// to allow subsequent operations.
return publishedTemplate.setETag(template.getETag());
}
return publishedTemplate.setETag(getETag(response));
}

private String getETag(IncomingHttpResponse response) {
List<String> etagList = (List<String>) response.getHeaders().get("etag");
checkState(etagList != null && !etagList.isEmpty(),
Expand All @@ -112,6 +150,10 @@ private String getETag(IncomingHttpResponse response) {
return etag;
}

private boolean isValidVersionNumber(String versionNumber) {
return !Strings.isNullOrEmpty(versionNumber) && versionNumber.matches("^\\d+$");
}

static FirebaseRemoteConfigClientImpl fromApp(FirebaseApp app) {
String projectId = ImplFirebaseTrampolines.getProjectId(app);
checkArgument(!Strings.isNullOrEmpty(projectId),
Expand Down
Loading