From 6ff4df7906bf6177ed9b6be1fbdf5a5110999c66 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Tue, 17 Jun 2025 13:07:38 +0530 Subject: [PATCH 01/13] Implementation for Fetching and Caching Server Side Remote Config --- .../firebase/remoteconfig/AndCondition.java | 63 ++++ .../remoteconfig/CustomSignalCondition.java | 141 ++++++++ .../remoteconfig/CustomSignalOperator.java | 54 +++ .../remoteconfig/FirebaseRemoteConfig.java | 67 ++++ .../FirebaseRemoteConfigClient.java | 5 +- .../FirebaseRemoteConfigClientImpl.java | 140 +++++--- .../firebase/remoteconfig/KeysAndValues.java | 135 ++++++++ .../remoteconfig/MicroPercentRange.java | 50 +++ .../firebase/remoteconfig/OneOfCondition.java | 140 ++++++++ .../firebase/remoteconfig/OrCondtion.java | 60 ++++ .../remoteconfig/PercentCondition.java | 164 +++++++++ .../PercentConditionOperator.java | 56 +++ .../remoteconfig/ServerCondition.java | 89 +++++ .../firebase/remoteconfig/ServerConfig.java | 102 ++++++ .../firebase/remoteconfig/ServerTemplate.java | 46 +++ .../remoteconfig/ServerTemplateData.java | 215 ++++++++++++ .../remoteconfig/ServerTemplateImpl.java | 176 ++++++++++ .../google/firebase/remoteconfig/Value.java | 135 ++++++++ .../firebase/remoteconfig/ValueSource.java | 30 ++ .../internal/ServerTemplateResponse.java | 320 ++++++++++++++++++ .../FirebaseRemoteConfigTest.java | 155 ++++++--- .../remoteconfig/MockRemoteConfigClient.java | 23 +- 22 files changed, 2269 insertions(+), 97 deletions(-) create mode 100644 src/main/java/com/google/firebase/remoteconfig/AndCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OrCondtion.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerConfig.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Value.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ValueSource.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java new file mode 100644 index 000000000..d64e7b2db --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -0,0 +1,63 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.AndConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +final class AndCondition { + private final ImmutableList conditions; + + AndCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for AND operation must not be null."); + checkArgument(!conditions.isEmpty(), + "List of conditions for AND operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + AndCondition(AndConditionResponse andConditionResponse) { + List conditionList = andConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation must not be null."); + checkArgument(!conditionList.isEmpty(), + "List of conditions for AND operation must not be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + List getConditions() { + return new ArrayList<>(conditions); + } + + AndConditionResponse toAndConditionResponse() { + return new AndConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java new file mode 100644 index 000000000..a6560eeec --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -0,0 +1,141 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; + +import java.util.ArrayList; +import java.util.List; + +final class CustomSignalCondition { + private final String customSignalKey; + private final CustomSignalOperator customSignalOperator; + private final ImmutableList targetCustomSignalValues; + + public CustomSignalCondition( + @NonNull String customSignalKey, + @NonNull CustomSignalOperator customSignalOperator, + @NonNull List targetCustomSignalValues) { + checkArgument( + !Strings.isNullOrEmpty(customSignalKey), "Custom signal key must not be null or empty."); + checkNotNull(customSignalOperator); + checkNotNull(targetCustomSignalValues); + checkArgument( + !targetCustomSignalValues.isEmpty(), "Target custom signal values must not be empty."); + this.customSignalKey = customSignalKey.trim(); + this.customSignalOperator = customSignalOperator; + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValues); + } + + CustomSignalCondition(CustomSignalConditionResponse customSignalCondition) { + checkArgument( + !Strings.isNullOrEmpty(customSignalCondition.getKey()), + "Custom signal key must not be null or empty."); + checkArgument( + !customSignalCondition.getTargetValues().isEmpty(), + "Target custom signal values must not be empty."); + this.customSignalKey = customSignalCondition.getKey().trim(); + List targetCustomSignalValuesList = customSignalCondition.getTargetValues(); + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValuesList); + switch (customSignalCondition.getOperator()) { + case "NUMERIC_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_EQUAL; + break; + case "NUMERIC_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_EQUAL; + break; + case "NUMERIC_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_THAN; + break; + case "NUMERIC_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_EQUAL; + break; + case "NUMERIC_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_THAN; + break; + case "NUMERIC_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_NOT_EQUAL; + break; + case "SEMANTIC_VERSION_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN; + break; + case "SEMANTIC_VERSION_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL; + break; + case "SEMANTIC_VERSION_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN; + break; + case "SEMANTIC_VERSION_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL; + break; + case "STRING_CONTAINS": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS; + break; + case "STRING_CONTAINS_REGEX": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS_REGEX; + break; + case "STRING_DOES_NOT_CONTAIN": + this.customSignalOperator = CustomSignalOperator.STRING_DOES_NOT_CONTAIN; + break; + case "STRING_EXACTLY_MATCHES": + this.customSignalOperator = CustomSignalOperator.STRING_EXACTLY_MATCHES; + break; + default: + this.customSignalOperator = CustomSignalOperator.UNSPECIFIED; + } + checkArgument( + this.customSignalOperator != CustomSignalOperator.UNSPECIFIED, + "Custom signal operator passed is invalid"); + } + + @NonNull + String getCustomSignalKey() { + return customSignalKey; + } + + @NonNull + CustomSignalOperator getCustomSignalOperator() { + return customSignalOperator; + } + + @NonNull + List getTargetCustomSignalValues() { + return new ArrayList<>(targetCustomSignalValues); + } + + CustomSignalConditionResponse toCustomConditonResponse() { + CustomSignalConditionResponse customSignalConditionResponse = + new CustomSignalConditionResponse(); + customSignalConditionResponse.setKey(this.customSignalKey); + customSignalConditionResponse.setOperator(this.customSignalOperator.getOperator()); + customSignalConditionResponse.setTargetValues(this.targetCustomSignalValues); + return customSignalConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java new file mode 100644 index 000000000..093d19cd0 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +enum CustomSignalOperator { + NUMERIC_EQUAL("NUMERIC_EQUAL"), + NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), + NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), + NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), + NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), + NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), + SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), + SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), + SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), + SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), + SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), + SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), + STRING_CONTAINS("STRING_CONTAINS"), + STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), + STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), + STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), + UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); + + private final String operator; + + CustomSignalOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + @NonNull + String getOperator() { + return operator; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index 41a0afbe4..b1786b478 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -101,6 +101,73 @@ protected Template execute() throws FirebaseRemoteConfigException { }; } + /** + * Alternative to {@link #getServerTemplate} where developers can initialize with a pre-cached + * template or config. + */ + public ServerTemplateImpl.Builder serverTemplateBuilder() { + return new ServerTemplateImpl.Builder(this.remoteConfigClient); + } + + /** + * Initializes a template instance and loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate(KeysAndValues defaultConfig) + throws FirebaseRemoteConfigException { + return getServerTemplateOp(defaultConfig).call(); + } + + /** + * Initializes a template instance without any defaults and loads the latest template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate() throws FirebaseRemoteConfigException { + return getServerTemplate(null); + } + + /** + * Initializes a template instance and asynchronously loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync(KeysAndValues defaultConfig) { + return getServerTemplateOp(defaultConfig).callAsync(app); + } + + /** + * Initializes a template instance without any defaults and asynchronously loads the latest + * template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync() { + return getServerTemplateAsync(null); + } + + private CallableOperation getServerTemplateOp( + KeysAndValues defaultConfig) { + return new CallableOperation() { + @Override + protected ServerTemplate execute() throws FirebaseRemoteConfigException { + String serverTemplateData = remoteConfigClient.getServerTemplate(); + ServerTemplate template = + serverTemplateBuilder() + .defaultConfig(defaultConfig) + .cachedTemplate(serverTemplateData) + .build(); + + return template; + } + }; + } + /** * Gets the requested version of the of the Remote Config template. * diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java index 9fdb596d6..d648fe750 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java @@ -40,4 +40,7 @@ Template publishTemplate(Template template, boolean validateOnly, ListVersionsResponse listVersions( ListVersionsOptions options) throws FirebaseRemoteConfigException; -} + + String getServerTemplate() throws FirebaseRemoteConfigException; + +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 7425673fb..b552c5013 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -38,30 +38,35 @@ 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.ServerTemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse; - import java.io.IOException; import java.util.List; import java.util.Map; -/** - * A helper class for interacting with Firebase Remote Config service. - */ +/** A helper class for interacting with Firebase Remote Config service. */ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient { - private static final String REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; + private static final String REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; + + private static final String SERVER_REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/firebase-server/serverRemoteConfig"; private static final Map COMMON_HEADERS = - ImmutableMap.of( - "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion(), - // There is a known issue in which the ETag is not properly returned in cases - // where the request does not specify a compression type. Currently, it is - // required to include the header `Accept-Encoding: gzip` or equivalent in all - // requests. https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates - "Accept-Encoding", "gzip" - ); + ImmutableMap.of( + "X-Firebase-Client", + "fire-admin-java/" + SdkUtils.getVersion(), + // There is a known issue in which the ETag is not properly returned in cases + // where the request does not specify a compression type. Currently, it is + // required to include the header `Accept-Encoding: gzip` or equivalent in all + // requests. + // https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates + "Accept-Encoding", + "gzip"); private final String remoteConfigUrl; + private final String serverRemoteConfigUrl; private final HttpRequestFactory requestFactory; private final JsonFactory jsonFactory; private final ErrorHandlingHttpClient httpClient; @@ -69,11 +74,13 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient private FirebaseRemoteConfigClientImpl(Builder builder) { checkArgument(!Strings.isNullOrEmpty(builder.projectId)); this.remoteConfigUrl = String.format(REMOTE_CONFIG_URL, builder.projectId); + this.serverRemoteConfigUrl = String.format(SERVER_REMOTE_CONFIG_URL, builder.projectId); this.requestFactory = checkNotNull(builder.requestFactory); this.jsonFactory = checkNotNull(builder.jsonFactory); HttpResponseInterceptor responseInterceptor = builder.responseInterceptor; RemoteConfigErrorHandler errorHandler = new RemoteConfigErrorHandler(this.jsonFactory); - this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler) + this.httpClient = + new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler) .setInterceptor(responseInterceptor); } @@ -82,6 +89,11 @@ String getRemoteConfigUrl() { return remoteConfigUrl; } + @VisibleForTesting + String getServerRemoteConfigUrl() { + return serverRemoteConfigUrl; + } + @VisibleForTesting HttpRequestFactory getRequestFactory() { return requestFactory; @@ -94,8 +106,8 @@ JsonFactory getJsonFactory() { @Override public Template getTemplate() throws FirebaseRemoteConfigException { - HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl) - .addAllHeaders(COMMON_HEADERS); + HttpRequestInfo request = + HttpRequestInfo.buildGetRequest(remoteConfigUrl).addAllHeaders(COMMON_HEADERS); IncomingHttpResponse response = httpClient.send(request); TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class); Template template = new Template(templateResponse); @@ -103,11 +115,25 @@ public Template getTemplate() throws FirebaseRemoteConfigException { } @Override - public Template getTemplateAtVersion( - @NonNull String versionNumber) throws FirebaseRemoteConfigException { - checkArgument(RemoteConfigUtil.isValidVersionNumber(versionNumber), - "Version number must be a non-empty string in int64 format."); - HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl) + public String getServerTemplate() throws FirebaseRemoteConfigException { + HttpRequestInfo request = + HttpRequestInfo.buildGetRequest(serverRemoteConfigUrl).addAllHeaders(COMMON_HEADERS); + IncomingHttpResponse response = httpClient.send(request); + ServerTemplateResponse templateResponse = httpClient.parse(response, + ServerTemplateResponse.class); + ServerTemplateData serverTemplateData = new ServerTemplateData(templateResponse); + serverTemplateData.setETag(getETag(response)); + return serverTemplateData.toJSON(); + } + + @Override + public Template getTemplateAtVersion(@NonNull String versionNumber) + throws FirebaseRemoteConfigException { + checkArgument( + RemoteConfigUtil.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); @@ -117,11 +143,15 @@ public Template getTemplateAtVersion( } @Override - public Template publishTemplate(@NonNull Template template, boolean validateOnly, - boolean forcePublish) throws FirebaseRemoteConfigException { + 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(false))) + HttpRequestInfo request = + HttpRequestInfo.buildRequest( + "PUT", + remoteConfigUrl, + new JsonHttpContent(jsonFactory, template.toTemplateResponse(false))) .addAllHeaders(COMMON_HEADERS) .addHeader("If-Match", forcePublish ? "*" : template.getETag()); if (validateOnly) { @@ -141,11 +171,12 @@ public Template publishTemplate(@NonNull Template template, boolean validateOnly @Override public Template rollback(@NonNull String versionNumber) throws FirebaseRemoteConfigException { - checkArgument(RemoteConfigUtil.isValidVersionNumber(versionNumber), - "Version number must be a non-empty string in int64 format."); + checkArgument( + RemoteConfigUtil.isValidVersionNumber(versionNumber), + "Version number must be a non-empty string in int64 format."); Map content = ImmutableMap.of("versionNumber", versionNumber); - HttpRequestInfo request = HttpRequestInfo - .buildJsonPostRequest(remoteConfigUrl + ":rollback", content) + HttpRequestInfo request = + HttpRequestInfo.buildJsonPostRequest(remoteConfigUrl + ":rollback", content) .addAllHeaders(COMMON_HEADERS); IncomingHttpResponse response = httpClient.send(request); TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class); @@ -154,9 +185,10 @@ public Template rollback(@NonNull String versionNumber) throws FirebaseRemoteCon } @Override - public TemplateResponse.ListVersionsResponse listVersions( - ListVersionsOptions options) throws FirebaseRemoteConfigException { - HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl + ":listVersions") + public TemplateResponse.ListVersionsResponse listVersions(ListVersionsOptions options) + throws FirebaseRemoteConfigException { + HttpRequestInfo request = + HttpRequestInfo.buildGetRequest(remoteConfigUrl + ":listVersions") .addAllHeaders(COMMON_HEADERS); if (options != null) { request.addAllParameters(options.wrapForTransport()); @@ -166,28 +198,30 @@ public TemplateResponse.ListVersionsResponse listVersions( private String getETag(IncomingHttpResponse response) { List etagList = (List) response.getHeaders().get("etag"); - checkState(etagList != null && !etagList.isEmpty(), - "ETag header is not available in the server response."); + checkState( + etagList != null && !etagList.isEmpty(), + "ETag header is not available in the server response."); String etag = etagList.get(0); - checkState(!Strings.isNullOrEmpty(etag), - "ETag header is not available in the server response."); + checkState( + !Strings.isNullOrEmpty(etag), "ETag header is not available in the server response."); return etag; } static FirebaseRemoteConfigClientImpl fromApp(FirebaseApp app) { String projectId = ImplFirebaseTrampolines.getProjectId(app); - checkArgument(!Strings.isNullOrEmpty(projectId), - "Project ID is required to access Remote Config service. Use a service " - + "account credential or set the project ID explicitly via FirebaseOptions. " - + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " - + "environment variable."); + checkArgument( + !Strings.isNullOrEmpty(projectId), + "Project ID is required to access Remote Config service. Use a service " + + "account credential or set the project ID explicitly via FirebaseOptions. " + + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " + + "environment variable."); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId(projectId) - .setRequestFactory(ApiClientUtils.newAuthorizedRequestFactory(app)) - .setJsonFactory(app.getOptions().getJsonFactory()) - .build(); + .setProjectId(projectId) + .setRequestFactory(ApiClientUtils.newAuthorizedRequestFactory(app)) + .setJsonFactory(app.getOptions().getJsonFactory()) + .build(); } static Builder builder() { @@ -201,7 +235,7 @@ static final class Builder { private JsonFactory jsonFactory; private HttpResponseInterceptor responseInterceptor; - private Builder() { } + private Builder() {} Builder setProjectId(String projectId) { this.projectId = projectId; @@ -218,8 +252,7 @@ Builder setJsonFactory(JsonFactory jsonFactory) { return this; } - Builder setResponseInterceptor( - HttpResponseInterceptor responseInterceptor) { + Builder setResponseInterceptor(HttpResponseInterceptor responseInterceptor) { this.responseInterceptor = responseInterceptor; return this; } @@ -230,7 +263,7 @@ FirebaseRemoteConfigClientImpl build() { } private static class RemoteConfigErrorHandler - extends AbstractPlatformErrorHandler { + extends AbstractPlatformErrorHandler { private RemoteConfigErrorHandler(JsonFactory jsonFactory) { super(jsonFactory); @@ -241,7 +274,7 @@ protected FirebaseRemoteConfigException createException(FirebaseException base) String response = getResponse(base); RemoteConfigServiceErrorResponse parsed = safeParse(response); return FirebaseRemoteConfigException.withRemoteConfigErrorCode( - base, parsed.getRemoteConfigErrorCode()); + base, parsed.getRemoteConfigErrorCode()); } private String getResponse(FirebaseException base) { @@ -255,8 +288,9 @@ private String getResponse(FirebaseException base) { private RemoteConfigServiceErrorResponse safeParse(String response) { if (!Strings.isNullOrEmpty(response)) { try { - return jsonFactory.createJsonParser(response) - .parseAndClose(RemoteConfigServiceErrorResponse.class); + return jsonFactory + .createJsonParser(response) + .parseAndClose(RemoteConfigServiceErrorResponse.class); } catch (IOException ignore) { // Ignore any error that may occur while parsing the error response. The server // may have responded with a non-json payload. @@ -266,4 +300,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) { return new RemoteConfigServiceErrorResponse(); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java new file mode 100644 index 000000000..36fdac361 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -0,0 +1,135 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents data stored in context passed to server-side Remote Config. + */ +public class KeysAndValues { + final ImmutableMap keysAndValues; + + private KeysAndValues(@NonNull Builder builder) { + keysAndValues = ImmutableMap.builder().putAll(builder.keysAndValues).build(); + } + + /** + * Checks whether a key is present in the context. + * + * @param key The key for data stored in context. + * @return Boolean representing whether the key passed is present in context. + */ + public boolean containsKey(String key) { + return keysAndValues.containsKey(key); + } + + /** + * Gets the value of the data stored in context. + * + * @param key The key for data stored in context. + * @return Value assigned to the key in context. + */ + public String get(String key) { + return keysAndValues.get(key); + } + + /** + * Builder class for KeysAndValues using which values will be assigned to + * private variables. + */ + public static class Builder { + // Holds the converted pairs of custom keys and values. + private final Map keysAndValues = new HashMap<>(); + + /** + * Adds a context data with string value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, @NonNull String value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + checkArgument(!Strings.isNullOrEmpty(value), "Context key must not be null or empty."); + keysAndValues.put(key, value); + return this; + } + + /** + * Adds a context data with boolean value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, boolean value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Boolean.toString(value)); + return this; + } + + /** + * Adds a context data with double value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, double value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Double.toString(value)); + return this; + } + + /** + * Adds a context data with long value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, long value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Long.toString(value)); + return this; + } + + /** + * Creates an instance of KeysAndValues with the values assigned through + * builder. + * + * @return instance of KeysAndValues + */ + @NonNull + public KeysAndValues build() { + return new KeysAndValues(this); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java new file mode 100644 index 000000000..a90205449 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -0,0 +1,50 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.MicroPercentRangeResponse; + +class MicroPercentRange { + private final int microPercentLowerBound; + private final int microPercentUpperBound; + + public MicroPercentRange(@Nullable Integer microPercentLowerBound, + @Nullable Integer microPercentUpperBound) { + this.microPercentLowerBound = microPercentLowerBound != null ? microPercentLowerBound : 0; + this.microPercentUpperBound = microPercentUpperBound != null ? microPercentUpperBound : 0; + } + + @NonNull + int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + @NonNull + int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + MicroPercentRangeResponse toMicroPercentRangeResponse() { + MicroPercentRangeResponse microPercentRangeResponse = new MicroPercentRangeResponse(); + microPercentRangeResponse.setMicroPercentLowerBound(this.microPercentLowerBound); + microPercentRangeResponse.setMicroPercentUpperBound(this.microPercentUpperBound); + return microPercentRangeResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java new file mode 100644 index 000000000..9084c84cf --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +class OneOfCondition { + private OrCondition orCondition; + private AndCondition andCondition; + private PercentCondition percent; + private CustomSignalCondition customSignal; + private String trueValue; + private String falseValue; + + OneOfCondition(OneOfConditionResponse oneOfconditionResponse) { + if (oneOfconditionResponse.getOrCondition() != null) { + this.orCondition = new OrCondition(oneOfconditionResponse.getOrCondition()); + } + if (oneOfconditionResponse.getAndCondition() != null) { + this.andCondition = new AndCondition(oneOfconditionResponse.getAndCondition()); + } + if (oneOfconditionResponse.getPercentCondition() != null) { + this.percent = new PercentCondition(oneOfconditionResponse.getPercentCondition()); + } + if (oneOfconditionResponse.getCustomSignalCondition() != null) { + this.customSignal = + new CustomSignalCondition(oneOfconditionResponse.getCustomSignalCondition()); + } + } + + @VisibleForTesting + OneOfCondition() { + this.orCondition = null; + this.andCondition = null; + this.percent = null; + this.customSignal = null; + this.trueValue = null; + this.falseValue = null; + } + + @Nullable + OrCondition getOrCondition() { + return orCondition; + } + + @Nullable + AndCondition getAndCondition() { + return andCondition; + } + + @Nullable + String isTrue() { + return trueValue; + } + + @Nullable + String isFalse() { + return falseValue; + } + + @Nullable + PercentCondition getPercent() { + return percent; + } + + @Nullable + CustomSignalCondition getCustomSignal() { + return customSignal; + } + + OneOfCondition setOrCondition(@NonNull OrCondition orCondition) { + checkNotNull(orCondition, "`Or` condition cannot be set to null."); + this.orCondition = orCondition; + return this; + } + + OneOfCondition setAndCondition(@NonNull AndCondition andCondition) { + checkNotNull(andCondition, "`And` condition cannot be set to null."); + this.andCondition = andCondition; + return this; + } + + OneOfCondition setPercent(@NonNull PercentCondition percent) { + checkNotNull(percent, "`Percent` condition cannot be set to null."); + this.percent = percent; + return this; + } + + OneOfCondition setCustomSignal(@NonNull CustomSignalCondition customSignal) { + checkNotNull(customSignal, "`Custom signal` condition cannot be set to null."); + this.customSignal = customSignal; + return this; + } + + OneOfCondition setTrue() { + this.trueValue = "true"; + return this; + } + + OneOfCondition setFalse() { + this.falseValue = "false"; + return this; + } + + OneOfConditionResponse toOneOfConditionResponse() { + OneOfConditionResponse oneOfConditionResponse = new OneOfConditionResponse(); + if (this.andCondition != null) { + oneOfConditionResponse.setAndCondition(this.andCondition.toAndConditionResponse()); + } + if (this.orCondition != null) { + oneOfConditionResponse.setOrCondition(this.orCondition.toOrConditionResponse()); + } + if (this.percent != null) { + oneOfConditionResponse.setPercentCondition(this.percent.toPercentConditionResponse()); + } + if (this.customSignal != null) { + oneOfConditionResponse.setCustomSignalCondition(this.customSignal.toCustomConditonResponse()); + } + return oneOfConditionResponse; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondtion.java b/src/main/java/com/google/firebase/remoteconfig/OrCondtion.java new file mode 100644 index 000000000..e3a2c1398 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondtion.java @@ -0,0 +1,60 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OrConditionResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +final class OrCondition { + private final ImmutableList conditions; + + public OrCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for OR operation must not be null."); + checkArgument(!conditions.isEmpty(), "List of conditions for OR operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + OrCondition(OrConditionResponse orConditionResponse) { + List conditionList = orConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation cannot be null."); + checkArgument(!conditionList.isEmpty(), "List of conditions for AND operation cannot be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + List getConditions() { + return new ArrayList<>(conditions); + } + + OrConditionResponse toOrConditionResponse() { + return new OrConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java new file mode 100644 index 000000000..a692d4f34 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -0,0 +1,164 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.PercentConditionResponse; + +/** Represents a condition that compares the instance pseudo-random percentile to a given limit. */ +public final class PercentCondition { + private int microPercent; + private MicroPercentRange microPercentRange; + private final PercentConditionOperator percentConditionOperator; + private final String seed; + + /** + * Create a percent condition for operator BETWEEN. + * + * @param microPercent The limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @Nullable Integer microPercent, + @NonNull PercentConditionOperator percentConditionOperator, + @NonNull String seed) { + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + checkArgument(!Strings.isNullOrEmpty(seed), "Seed must not be null or empty."); + this.microPercent = microPercent != null ? microPercent : 0; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Create a percent condition for operators GREATER_THAN and LESS_OR_EQUAL. + * + * @param microPercentRange The micro-percent interval to be used with the BETWEEN operator. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @NonNull MicroPercentRange microPercentRange, + @NonNull PercentConditionOperator percentConditionOperator, + String seed) { + checkNotNull(microPercentRange, "Percent range must not be null."); + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + this.microPercentRange = microPercentRange; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Creates a new {@link PercentCondition} from API response. + * + * @param percentCondition the conditions obtained from server call. + */ + PercentCondition(PercentConditionResponse percentCondition) { + checkArgument( + !Strings.isNullOrEmpty(percentCondition.getSeed()), "Seed must not be empty or null"); + this.microPercent = percentCondition.getMicroPercent(); + this.seed = percentCondition.getSeed(); + switch (percentCondition.getPercentOperator()) { + case "BETWEEN": + this.percentConditionOperator = PercentConditionOperator.BETWEEN; + break; + case "GREATER_THAN": + this.percentConditionOperator = PercentConditionOperator.GREATER_THAN; + break; + case "LESS_OR_EQUAL": + this.percentConditionOperator = PercentConditionOperator.LESS_OR_EQUAL; + break; + default: + this.percentConditionOperator = PercentConditionOperator.UNSPECIFIED; + } + checkArgument( + this.percentConditionOperator != PercentConditionOperator.UNSPECIFIED, + "Percentage operator is invalid"); + if (percentCondition.getMicroPercentRange() != null) { + this.microPercentRange = + new MicroPercentRange( + percentCondition.getMicroPercentRange().getMicroPercentLowerBound(), + percentCondition.getMicroPercentRange().getMicroPercentUpperBound()); + } + } + + /** + * Gets the limit of percentiles to target in micro-percents when using the LESS_OR_EQUAL and + * GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * + * @return micro percent. + */ + @Nullable + public int getMicroPercent() { + return microPercent; + } + + /** + * Gets micro-percent interval to be used with the BETWEEN operator. + * + * @return micro percent range. + */ + @Nullable + public MicroPercentRange getMicroPercentRange() { + return microPercentRange; + } + + /** + * Gets choice of percent operator to determine how to compare targets to percent(s). + * + * @return operator. + */ + @NonNull + public PercentConditionOperator getPercentConditionOperator() { + return percentConditionOperator; + } + + /** + * The seed used when evaluating the hash function to map an instance to a value in the hash + * space. This is a string which can have 0 - 32 characters and can contain ASCII characters + * [-_.0-9a-zA-Z].The string is case-sensitive. + * + * @return seed. + */ + @NonNull + public String getSeed() { + return seed; + } + + PercentConditionResponse toPercentConditionResponse() { + PercentConditionResponse percentConditionResponse = new PercentConditionResponse(); + percentConditionResponse.setMicroPercent(this.microPercent); + percentConditionResponse.setMicroPercentRange( + this.microPercentRange.toMicroPercentRangeResponse()); + percentConditionResponse.setPercentOperator(this.percentConditionOperator.getOperator()); + percentConditionResponse.setSeed(this.seed); + return percentConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java new file mode 100644 index 000000000..72240b2c9 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -0,0 +1,56 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +/** + * Defines supported operators for percent conditions. + */ +public enum PercentConditionOperator { + BETWEEN("BETWEEN"), + GREATER_THAN("GREATER_THAN"), + LESS_OR_EQUAL("LESS_OR_EQUAL"), + UNSPECIFIED("PERCENT_OPERATOR_UNSPECIFIED"); + + private final String operator; + + /** + * Creates percent condition operator. + * + * @param operator The choice of percent operator to determine how to compare targets to + * percent(s). + */ + PercentConditionOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + /** + * Gets percent condition operator. + * + * @return operator. + */ + @NonNull + public String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java new file mode 100644 index 000000000..fa8eb32c5 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.ServerConditionResponse; + +import java.util.Objects; + +final class ServerCondition { + + private String name; + private OneOfCondition serverCondition; + + ServerCondition(@NonNull String name, @NonNull OneOfCondition condition) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + this.serverCondition = condition; + } + + ServerCondition(@NonNull ServerConditionResponse serverConditionResponse) { + checkNotNull(serverConditionResponse); + this.name = serverConditionResponse.getName(); + this.serverCondition = new OneOfCondition(serverConditionResponse.getServerCondition()); + } + + @NonNull + String getName() { + return name; + } + + @NonNull + OneOfCondition getCondition() { + return serverCondition; + } + + ServerCondition setName(@NonNull String name) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + return this; + } + + ServerCondition setServerCondition(@NonNull OneOfCondition condition) { + checkNotNull(condition, "condition must not be null or empty"); + this.serverCondition = condition; + return this; + } + + ServerConditionResponse toServerConditionResponse() { + return new ServerConditionResponse().setName(this.name) + .setServerCondition(this.serverCondition.toOneOfConditionResponse()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerCondition condition = (ServerCondition) o; + return Objects.equals(name, condition.name) + && Objects.equals(serverCondition, condition.serverCondition); + } + + @Override + public int hashCode() { + return Objects.hash(name, serverCondition); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java new file mode 100644 index 000000000..a498fb994 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +import java.util.Map; + +/** + * Represents the configuration produced by evaluating a server template. + */ +public final class ServerConfig { + private final Map configValues; + + ServerConfig(Map configValues) { + this.configValues = configValues; + } + + /** + * Gets the value for the given key as a string. Convenience method for calling + * serverConfig.getValue(key).asString(). + * + * @param key The name of the parameter. + * @return config value for the given key as string. + */ + @NonNull + public String getString(@NonNull String key) { + return this.getValue(key).asString(); + } + + /** + * Gets the value for the given key as a boolean.Convenience method for calling + * serverConfig.getValue(key).asBoolean(). + * + * @param key The name of the parameter. + * @return config value for the given key as boolean. + */ + @NonNull + public boolean getBoolean(@NonNull String key) { + return this.getValue(key).asBoolean(); + } + + /** + * Gets the value for the given key as long.Convenience method for calling + * serverConfig.getValue(key).asLong(). + * + * @param key The name of the parameter. + * @return config value for the given key as long. + */ + @NonNull + public long getLong(@NonNull String key) { + return this.getValue(key).asLong(); + } + + /** + * Gets the value for the given key as double.Convenience method for calling + * serverConfig.getValue(key).asDouble(). + * + * @param key The name of the parameter. + * @return config value for the given key as double. + */ + @NonNull + public double getDouble(@NonNull String key) { + return this.getValue(key).asDouble(); + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @param key The name of the parameter. + * @return config value source for the given key. + */ + @NonNull + public ValueSource getValueSource(@NonNull String key) { + return this.getValue(key).getSource(); + } + + private Value getValue(String key) { + checkArgument(!Strings.isNullOrEmpty(key), "Server config key cannot be null or empty."); + if (configValues.containsKey(key)) { + return configValues.get(key); + } + return new Value(ValueSource.STATIC); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java new file mode 100644 index 000000000..2ea8b8289 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import com.google.api.core.ApiFuture; + +public interface ServerTemplate { + public interface Builder { + + Builder defaultConfig(KeysAndValues config); + + Builder cachedTemplate(String templateJson); + + ServerTemplate build(); + } + /** + * Proccess the template data with a condition evaluator + * based on the provided context. + */ + ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException; + /** + * Proccess the template data without context. + */ + ServerConfig evaluate() throws FirebaseRemoteConfigException; + + /** + * Fetches and caches the current active version of the project. + */ + ApiFuture load() throws FirebaseRemoteConfigException; + + String toJson(); +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java new file mode 100644 index 000000000..344046862 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -0,0 +1,215 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.JsonFactory; +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +final class ServerTemplateData { + + private String etag; + private Map parameters; + private List serverConditions; + private Map parameterGroups; + private Version version; + + + ServerTemplateData(String etag) { + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + this.etag = etag; + } + + ServerTemplateData() { + this((String) null); + } + + ServerTemplateData(@NonNull ServerTemplateResponse serverTemplateResponse) { + checkNotNull(serverTemplateResponse); + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + if (serverTemplateResponse.getParameters() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameters().entrySet()) { + this.parameters.put(entry.getKey(), new Parameter(entry.getValue())); + } + } + if (serverTemplateResponse.getServerConditions() != null) { + for (ServerTemplateResponse.ServerConditionResponse conditionResponse : + serverTemplateResponse.getServerConditions()) { + this.serverConditions.add(new ServerCondition(conditionResponse)); + } + } + if (serverTemplateResponse.getParameterGroups() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameterGroups().entrySet()) { + this.parameterGroups.put(entry.getKey(), new ParameterGroup(entry.getValue())); + } + } + if (serverTemplateResponse.getVersion() != null) { + this.version = new Version(serverTemplateResponse.getVersion()); + } + this.etag = serverTemplateResponse.getEtag(); + } + + + static ServerTemplateData fromJSON(@NonNull String json) + throws FirebaseRemoteConfigException { + checkArgument(!Strings.isNullOrEmpty(json), "JSON String must not be null or empty."); + // using the default json factory as no rpc calls are made here + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + ServerTemplateResponse serverTemplateResponse = + jsonFactory.createJsonParser(json).parseAndClose(ServerTemplateResponse.class); + return new ServerTemplateData(serverTemplateResponse); + } catch (IOException e) { + throw new FirebaseRemoteConfigException( + ErrorCode.INVALID_ARGUMENT, "Unable to parse JSON string."); + } + } + + + String getETag() { + return this.etag; + } + + + @NonNull + public Map getParameters() { + return this.parameters; + } + + @NonNull + List getServerConditions() { + return serverConditions; + } + + @NonNull + Map getParameterGroups() { + return parameterGroups; + } + + Version getVersion() { + return version; + } + + ServerTemplateData setParameters(@NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } + + + ServerTemplateData setServerConditions(@NonNull List conditions) { + checkNotNull(conditions, "conditions must not be null."); + this.serverConditions = conditions; + return this; + } + + ServerTemplateData setParameterGroups( + @NonNull Map parameterGroups) { + checkNotNull(parameterGroups, "parameter groups must not be null."); + this.parameterGroups = parameterGroups; + return this; + } + + ServerTemplateData setVersion(Version version) { + this.version = version; + return this; + } + + String toJSON() { + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + return jsonFactory.toString(this.toServerTemplateResponse(true)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + ServerTemplateData setETag(String etag) { + this.etag = etag; + return this; + } + + ServerTemplateResponse toServerTemplateResponse(boolean includeAll) { + Map parameterResponses = new HashMap<>(); + for (Map.Entry entry : this.parameters.entrySet()) { + parameterResponses.put(entry.getKey(), entry.getValue().toParameterResponse()); + } + List serverConditionResponses = + new ArrayList<>(); + for (ServerCondition condition : this.serverConditions) { + serverConditionResponses.add(condition.toServerConditionResponse()); + } + Map parameterGroupResponse = new HashMap<>(); + for (Map.Entry entry : this.parameterGroups.entrySet()) { + parameterGroupResponse.put(entry.getKey(), entry.getValue().toParameterGroupResponse()); + } + TemplateResponse.VersionResponse versionResponse = + (this.version == null) ? null : this.version.toVersionResponse(includeAll); + ServerTemplateResponse serverTemplateResponse = + new ServerTemplateResponse() + .setParameters(parameterResponses) + .setServerConditions(serverConditionResponses) + .setParameterGroups(parameterGroupResponse) + .setVersion(versionResponse); + if (includeAll) { + return serverTemplateResponse.setEtag(this.etag); + } + return serverTemplateResponse; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerTemplateData template = (ServerTemplateData) o; + return Objects.equals(etag, template.etag) + && Objects.equals(parameters, template.parameters) + && Objects.equals(serverConditions, template.serverConditions) + && Objects.equals(parameterGroups, template.parameterGroups) + && Objects.equals(version, template.version); + } + + @Override + public int hashCode() { + return Objects.hash(etag, parameters, serverConditions, parameterGroups, version); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java new file mode 100644 index 000000000..1afe4037c --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java @@ -0,0 +1,176 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ServerTemplateImpl implements ServerTemplate { + + private final KeysAndValues defaultConfig; + private FirebaseRemoteConfigClient client; + private ServerTemplateData cache; + private final AtomicReference cachedTemplate; // Added field for cached template + private static final Logger logger = LoggerFactory.getLogger(ServerTemplate.class); + + public static class Builder implements ServerTemplate.Builder { + private KeysAndValues defaultConfig; + private String cachedTemplate; + private FirebaseRemoteConfigClient client; + + Builder(FirebaseRemoteConfigClient remoteConfigClient) { + this.client = remoteConfigClient; + } + + @Override + public Builder defaultConfig(KeysAndValues config) { + this.defaultConfig = config; + return this; + } + + @Override + public Builder cachedTemplate(String templateJson) { + this.cachedTemplate = templateJson; + return this; + } + + @Override + public ServerTemplate build() { + return new ServerTemplateImpl(this); + } + } + + private ServerTemplateImpl(Builder builder) { + this.defaultConfig = builder.defaultConfig; + this.cachedTemplate = new AtomicReference<>(builder.cachedTemplate); + this.client = builder.client; + try { + this.cache = ServerTemplateData.fromJSON(this.cachedTemplate.get()); + } catch (FirebaseRemoteConfigException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + //TODO : Implementation of Evaluate will be in another PR + @Override + public ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException { + + Map configValues = new HashMap<>(); + return new ServerConfig(configValues); + } + + @Override + public ServerConfig evaluate() throws FirebaseRemoteConfigException { + KeysAndValues context = new KeysAndValues.Builder().build(); + return evaluate(context); + } + + @Override + public ApiFuture load() throws FirebaseRemoteConfigException { + String serverTemplate = client.getServerTemplate(); + this.cachedTemplate.set(serverTemplate); + this.cache = ServerTemplateData.fromJSON(serverTemplate); + return ApiFutures.immediateFuture(null); + } + + // Add getters or other methods as needed + public KeysAndValues getDefaultConfig() { + return defaultConfig; + } + + public String getCachedTemplate() { + return cachedTemplate.get(); + } + + @Override + public String toJson() { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + return gson.toJson(this.cache); + } + + private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, + ImmutableMap parameters, Map configValues) { + // Overlays config Value objects derived by evaluating the template. + for (String parameterName : parameters.keySet()) { + Parameter parameter = parameters.get(parameterName); + if (parameter == null) { + logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); + continue; + } + + ImmutableMap conditionalValues = ImmutableMap.copyOf( + parameter.getConditionalValues()); + ParameterValue derivedValue = null; + + // Iterates in order over condition list. If there is a value associated + // with a condition, this checks if the condition is true. + for (String conditionName : evaluatedCondition.keySet()) { + boolean conditionEvaluation = evaluatedCondition.get(conditionName); + if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { + derivedValue = conditionalValues.get(conditionName); + break; + } + } + + if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { + logger.warn( + String.format("Derived value found for %s but parameter is set to use in app default.", + parameterName)); + continue; + } + + if (derivedValue != null) { + String parameterValue = derivedValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterValue); + configValues.put(parameterName, value); + continue; + } + + ParameterValue defaultValue = parameter.getDefaultValue(); + if (defaultValue == null) { + logger.warn(String.format("Default parameter value for %s is not set.", + parameterName)); + continue; + } + + ParameterValueResponse defaultValueResponse = defaultValue.toParameterValueResponse(); + if (defaultValueResponse != null && defaultValueResponse.isUseInAppDefault()) { + logger.info(String.format("Default value for %s is set to use in app default.", + parameterName)); + continue; + } + + String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); + configValues.put(parameterName, value); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java new file mode 100644 index 000000000..aecc98c1a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps a parameter value with metadata and type-safe getters. Type-safe + * getters insulate application logic from remote changes to parameter names and + * types. + */ +class Value { + private static final Logger logger = LoggerFactory.getLogger(Value.class); + private static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false; + private static final String DEFAULT_VALUE_FOR_STRING = ""; + private static final long DEFAULT_VALUE_FOR_LONG = 0; + private static final double DEFAULT_VALUE_FOR_DOUBLE = 0; + private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", + "t", "yes", "y", "on"); + + private final ValueSource source; + private final String value; + + /** + * Creates a new {@link Value} object. + * + * @param source Indicates the source of a value. + * @param value Indicates a parameter value. + */ + Value(@NonNull ValueSource source, String value) { + checkNotNull(source, "Value source cannot be null."); + this.source = source; + this.value = value; + } + + /** + * Creates a new {@link Value} object with default value. + * + * @param source Indicates the source of a value. + */ + Value(@NonNull ValueSource source) { + this(source, DEFAULT_VALUE_FOR_STRING); + } + + /** + * Gets the value as a string. + * + * @return value as string + */ + @NonNull + String asString() { + return this.value; + } + + /** + * Gets the value as a boolean.The following values (case + * insensitive) are interpreted as true: "1", "true", "t", "yes", "y", "on". + * Other values are interpreted as false. + * + * @return value as boolean + */ + @NonNull + boolean asBoolean() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_BOOLEAN; + } + return BOOLEAN_TRUTHY_VALUES.contains(value.toLowerCase()); + } + + /** + * Gets the value as long. Comparable to calling Number(value) || 0. + * + * @return value as long + */ + @NonNull + long asLong() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_LONG; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert %s to long type.", value); + return DEFAULT_VALUE_FOR_LONG; + } + } + + /** + * Gets the value as double. Comparable to calling Number(value) || 0. + * + * @return value as double + */ + @NonNull + double asDouble() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_DOUBLE; + } + try { + return Double.parseDouble(this.value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert %s to double type.", value); + return DEFAULT_VALUE_FOR_DOUBLE; + } + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @return source. + */ + @NonNull + ValueSource getSource() { + return source; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java new file mode 100644 index 000000000..2f7f9487a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java @@ -0,0 +1,30 @@ + +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +/** + * Indicates the source of a value. + * "static" indicates the value was defined by a static constant. + * "default" indicates the value was defined by default config. + * "remote" indicates the value was defined by config produced by evaluating a template. + */ +public enum ValueSource { + STATIC, + REMOTE, + DEFAULT +} diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java new file mode 100644 index 000000000..cf12c00a2 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -0,0 +1,320 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig.internal; + +import com.google.api.client.util.Key; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; + +import java.util.List; +import java.util.Map; + +/** + * The Data Transfer Object for parsing Remote Config template responses from the Remote Config + * service. + */ +public final class ServerTemplateResponse { + @Key("parameters") + private Map parameters; + + @Key("conditions") + private List serverConditions; + + @Key("parameterGroups") + private Map parameterGroups; + + @Key("version") + private VersionResponse version; + + // For local JSON serialization and deserialization purposes only. + // ETag in response type is never set by the HTTP response. + @Key("etag") + private String etag; + + public Map getParameters() { + return parameters; + } + + public List getServerConditions() { + return serverConditions; + } + + public Map getParameterGroups() { + return parameterGroups; + } + + public VersionResponse getVersion() { + return version; + } + + public String getEtag() { + return etag; + } + + public ServerTemplateResponse setParameters(Map parameters) { + this.parameters = parameters; + return this; + } + + public ServerTemplateResponse setServerConditions( + List serverConditions) { + this.serverConditions = serverConditions; + return this; + } + + public ServerTemplateResponse setParameterGroups( + Map parameterGroups) { + this.parameterGroups = parameterGroups; + return this; + } + + public ServerTemplateResponse setVersion(VersionResponse version) { + this.version = version; + return this; + } + + public ServerTemplateResponse setEtag(String etag) { + this.etag = etag; + return this; + } + + /** + * The Data Transfer Object for parsing Remote Config condition responses from the Remote Config + * service. + */ + public static final class ServerConditionResponse { + + @Key("name") + private String name; + + @Key("condition") + private OneOfConditionResponse condition; + + public String getName() { + return name; + } + + public OneOfConditionResponse getServerCondition() { + return condition; + } + + public ServerConditionResponse setName(String name) { + this.name = name; + return this; + } + + public ServerConditionResponse setServerCondition(OneOfConditionResponse condition) { + this.condition = condition; + return this; + } + } + + public static final class OneOfConditionResponse { + @Key("orCondition") + private OrConditionResponse orCondition; + + @Key("andCondition") + private AndConditionResponse andCondition; + + @Key("customSignal") + private CustomSignalConditionResponse customSignalCondition; + + @Key("percent") + private PercentConditionResponse percentCondition; + + public OrConditionResponse getOrCondition() { + return orCondition; + } + + public AndConditionResponse getAndCondition() { + return andCondition; + } + + public PercentConditionResponse getPercentCondition() { + return percentCondition; + } + + public CustomSignalConditionResponse getCustomSignalCondition() { + return customSignalCondition; + } + + public OneOfConditionResponse setOrCondition(OrConditionResponse orCondition) { + this.orCondition = orCondition; + return this; + } + + public OneOfConditionResponse setAndCondition(AndConditionResponse andCondition) { + this.andCondition = andCondition; + return this; + } + + public OneOfConditionResponse setCustomSignalCondition( + CustomSignalConditionResponse customSignalCondition) { + this.customSignalCondition = customSignalCondition; + return this; + } + + public OneOfConditionResponse setPercentCondition(PercentConditionResponse percentCondition) { + this.percentCondition = percentCondition; + return this; + } + } + + public static final class OrConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public OrConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class AndConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public AndConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class CustomSignalConditionResponse { + @Key("customSignalOperator") + private String operator; + + @Key("customSignalKey") + private String key; + + @Key("targetCustomSignalValues") + private List targetValues; + + public String getOperator() { + return operator; + } + + public String getKey() { + return key; + } + + public List getTargetValues() { + return targetValues; + } + + public CustomSignalConditionResponse setOperator(String operator) { + this.operator = operator; + return this; + } + + public CustomSignalConditionResponse setKey(String key) { + this.key = key; + return this; + } + + public CustomSignalConditionResponse setTargetValues(List targetValues) { + this.targetValues = targetValues; + return this; + } + } + + public static final class PercentConditionResponse { + @Key("microPercent") + private int microPercent; + + @Key("microPercentRange") + private MicroPercentRangeResponse microPercentRange; + + @Key("percentOperator") + private String percentOperator; + + @Key("seed") + private String seed; + + public int getMicroPercent() { + return microPercent; + } + + public MicroPercentRangeResponse getMicroPercentRange() { + return microPercentRange; + } + + public String getPercentOperator() { + return percentOperator; + } + + public String getSeed() { + return seed; + } + + public PercentConditionResponse setMicroPercent(int microPercent) { + this.microPercent = microPercent; + return this; + } + + public PercentConditionResponse setMicroPercentRange( + MicroPercentRangeResponse microPercentRange) { + this.microPercentRange = microPercentRange; + return this; + } + + public PercentConditionResponse setPercentOperator(String percentOperator) { + this.percentOperator = percentOperator; + return this; + } + + public PercentConditionResponse setSeed(String seed) { + this.seed = seed; + return this; + } + } + + public static final class MicroPercentRangeResponse { + @Key("microPercentLowerBound") + private int microPercentLowerBound; + + @Key("microPercentUpperBound") + private int microPercentUpperBound; + + public int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + public int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + public MicroPercentRangeResponse setMicroPercentLowerBound(int microPercentLowerBound) { + this.microPercentLowerBound = microPercentLowerBound; + return this; + } + + public MicroPercentRangeResponse setMicroPercentUpperBound(int microPercentUpperBound) { + this.microPercentUpperBound = microPercentUpperBound; + return this; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index d3e7fbff2..40d569294 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -27,22 +27,30 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; - import com.google.firebase.remoteconfig.internal.TemplateResponse; - import java.util.concurrent.ExecutionException; - import org.junit.After; import org.junit.Test; +/** Unit tests + * for {@link FirebaseRemoteConfig}. + * */ public class FirebaseRemoteConfigTest { - private static final FirebaseOptions TEST_OPTIONS = FirebaseOptions.builder() + private static final FirebaseOptions TEST_OPTIONS = + FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); + private static final String TEST_SERVER_TEMPLATE = + "{\n" + + " \"etag\": \"etag-123456789012-1\",\n" + + " \"parameters\": {},\n" + + " \"serverConditions\": [],\n" + + " \"parameterGroups\": {}\n" + + "}"; private static final FirebaseRemoteConfigException TEST_EXCEPTION = - new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); + new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); @After public void tearDown() { @@ -76,10 +84,25 @@ public void testDefaultRemoteConfigClient() { assertTrue(client instanceof FirebaseRemoteConfigClientImpl); assertSame(client, remoteConfig.getRemoteConfigClient()); - String expectedUrl = "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getRemoteConfigUrl()); } + @Test + public void testDefaultServerRemoteConfigClient() { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); + FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app); + + FirebaseRemoteConfigClient client = remoteConfig.getRemoteConfigClient(); + + assertTrue(client instanceof FirebaseRemoteConfigClientImpl); + assertSame(client, remoteConfig.getRemoteConfigClient()); + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/namespaces/firebase-server/serverRemoteConfig"; + assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getServerRemoteConfigUrl()); + } + @Test public void testAppDelete() { FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); @@ -98,16 +121,16 @@ public void testAppDelete() { @Test public void testRemoteConfigClientWithoutProjectId() { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .build(); + FirebaseOptions options = + FirebaseOptions.builder().setCredentials(new MockGoogleCredentials("test-token")).build(); FirebaseApp.initializeApp(options); try { FirebaseRemoteConfig.getInstance(); fail("No error thrown for missing project ID"); } catch (IllegalArgumentException expected) { - String message = "Project ID is required to access Remote Config service. Use a service " + String message = + "Project ID is required to access Remote Config service. Use a service " + "account credential or set the project ID explicitly via FirebaseOptions. " + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " + "environment variable."; @@ -121,8 +144,8 @@ public void testRemoteConfigClientWithoutProjectId() { @Test public void testGetTemplate() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplate(); @@ -144,8 +167,8 @@ public void testGetTemplateFailure() { @Test public void testGetTemplateAsync() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAsync().get(); @@ -169,8 +192,8 @@ public void testGetTemplateAsyncFailure() throws InterruptedException { @Test public void testGetTemplateAtVersionWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion("64"); @@ -192,8 +215,8 @@ public void testGetTemplateAtVersionWithStringValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync("55").get(); @@ -215,8 +238,8 @@ public void testGetTemplateAtVersionAsyncWithStringValueFailure() throws Interru @Test public void testGetTemplateAtVersionWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion(64L); @@ -238,8 +261,8 @@ public void testGetTemplateAtVersionWithLongValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync(55L).get(); @@ -407,8 +430,8 @@ public void testForcePublishTemplateAsyncFailure() throws InterruptedException { @Test public void testRollbackWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback("64"); @@ -430,8 +453,8 @@ public void testRollbackWithStringValueFailure() { @Test public void testRollbackAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync("55").get(); @@ -453,8 +476,8 @@ public void testRollbackAsyncWithStringValueFailure() throws InterruptedExceptio @Test public void testRollbackWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback(64L); @@ -476,8 +499,8 @@ public void testRollbackWithLongValueFailure() { @Test public void testRollbackAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync(55L).get(); @@ -501,7 +524,8 @@ public void testRollbackAsyncWithLongValueFailure() throws InterruptedException @Test public void testListVersionsWithNoOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -524,7 +548,8 @@ public void testListVersionsWithNoOptionsFailure() { @Test public void testListVersionsAsyncWithNoOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -547,12 +572,13 @@ public void testListVersionsAsyncWithNoOptionsFailure() throws InterruptedExcept @Test public void testListVersionsWithOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersions( - ListVersionsOptions.builder().build()); + ListVersionsPage listVersionsPage = + remoteConfig.listVersions(ListVersionsOptions.builder().build()); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -571,12 +597,13 @@ public void testListVersionsWithOptionsFailure() { @Test public void testListVersionsAsyncWithOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersionsAsync( - ListVersionsOptions.builder().build()).get(); + ListVersionsPage listVersionsPage = + remoteConfig.listVersionsAsync(ListVersionsOptions.builder().build()).get(); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -597,4 +624,54 @@ private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS); return new FirebaseRemoteConfig(app, client); } -} + + // Get Server template tests + + @Test + public void testGetServerTemplate() throws FirebaseRemoteConfigException { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplate(); + String templateData = template.toJson(); + assertEquals(TEST_SERVER_TEMPLATE, templateData); + } + + @Test + public void testGetServerTemplateFailure() { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplate(); + } catch (FirebaseRemoteConfigException e) { + assertSame(TEST_EXCEPTION, e); + } + } + + @Test + public void testGetServerTemplateAsync() throws Exception { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplateAsync().get(); + String templateData = template.toJson(); + assertEquals(TEST_SERVER_TEMPLATE, templateData); + } + + @Test + public void testGetServerTemplateAsyncFailure() throws InterruptedException { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplateAsync().get(); + } catch (ExecutionException e) { + assertSame(TEST_EXCEPTION, e.getCause()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 9ca58508d..1ff31ebb8 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -21,28 +21,35 @@ public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{ private final Template resultTemplate; + private final String resultServerTemplate; private final FirebaseRemoteConfigException exception; private final ListVersionsResponse listVersionsResponse; private MockRemoteConfigClient(Template resultTemplate, + String resultServerTemplate, ListVersionsResponse listVersionsResponse, FirebaseRemoteConfigException exception) { this.resultTemplate = resultTemplate; + this.resultServerTemplate = resultServerTemplate; this.listVersionsResponse = listVersionsResponse; this.exception = exception; } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { - return new MockRemoteConfigClient(resultTemplate, null, null); + return new MockRemoteConfigClient(resultTemplate,null, null, null); + } + + static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { + return new MockRemoteConfigClient(null, resultServerTemplate,null, null); } static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null, listVersionsResponse, null); + return new MockRemoteConfigClient(null,null, listVersionsResponse, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null, null, exception); + return new MockRemoteConfigClient(null,null, null, exception); } @Override @@ -53,6 +60,14 @@ public Template getTemplate() throws FirebaseRemoteConfigException { return resultTemplate; } + @Override + public String getServerTemplate() throws FirebaseRemoteConfigException { + if (exception != null) { + throw exception; + } + return resultServerTemplate; + } + @Override public Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException { if (exception != null) { @@ -86,4 +101,4 @@ public ListVersionsResponse listVersions( } return listVersionsResponse; } -} +} \ No newline at end of file From 858e0e7beaa19eee6f8f7f5f8512652293706a81 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Wed, 18 Jun 2025 12:56:20 +0530 Subject: [PATCH 02/13] implementation of fetch , cache and load of template --- .../remoteconfig/CustomSignalCondition.java | 141 --------------- .../remoteconfig/CustomSignalOperator.java | 54 ------ .../remoteconfig/MicroPercentRange.java | 50 ------ .../firebase/remoteconfig/OneOfCondition.java | 39 ----- .../remoteconfig/PercentCondition.java | 164 ------------------ .../PercentConditionOperator.java | 56 ------ .../FirebaseRemoteConfigClientImplTest.java | 1 - 7 files changed, 505 deletions(-) delete mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java delete mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java delete mode 100644 src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java delete mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentCondition.java delete mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java deleted file mode 100644 index a6560eeec..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java +++ /dev/null @@ -1,141 +0,0 @@ - -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.remoteconfig; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.firebase.internal.NonNull; -import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; - -import java.util.ArrayList; -import java.util.List; - -final class CustomSignalCondition { - private final String customSignalKey; - private final CustomSignalOperator customSignalOperator; - private final ImmutableList targetCustomSignalValues; - - public CustomSignalCondition( - @NonNull String customSignalKey, - @NonNull CustomSignalOperator customSignalOperator, - @NonNull List targetCustomSignalValues) { - checkArgument( - !Strings.isNullOrEmpty(customSignalKey), "Custom signal key must not be null or empty."); - checkNotNull(customSignalOperator); - checkNotNull(targetCustomSignalValues); - checkArgument( - !targetCustomSignalValues.isEmpty(), "Target custom signal values must not be empty."); - this.customSignalKey = customSignalKey.trim(); - this.customSignalOperator = customSignalOperator; - this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValues); - } - - CustomSignalCondition(CustomSignalConditionResponse customSignalCondition) { - checkArgument( - !Strings.isNullOrEmpty(customSignalCondition.getKey()), - "Custom signal key must not be null or empty."); - checkArgument( - !customSignalCondition.getTargetValues().isEmpty(), - "Target custom signal values must not be empty."); - this.customSignalKey = customSignalCondition.getKey().trim(); - List targetCustomSignalValuesList = customSignalCondition.getTargetValues(); - this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValuesList); - switch (customSignalCondition.getOperator()) { - case "NUMERIC_EQUAL": - this.customSignalOperator = CustomSignalOperator.NUMERIC_EQUAL; - break; - case "NUMERIC_GREATER_EQUAL": - this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_EQUAL; - break; - case "NUMERIC_GREATER_THAN": - this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_THAN; - break; - case "NUMERIC_LESS_EQUAL": - this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_EQUAL; - break; - case "NUMERIC_LESS_THAN": - this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_THAN; - break; - case "NUMERIC_NOT_EQUAL": - this.customSignalOperator = CustomSignalOperator.NUMERIC_NOT_EQUAL; - break; - case "SEMANTIC_VERSION_EQUAL": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_EQUAL; - break; - case "SEMANTIC_VERSION_GREATER_EQUAL": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL; - break; - case "SEMANTIC_VERSION_GREATER_THAN": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN; - break; - case "SEMANTIC_VERSION_LESS_EQUAL": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL; - break; - case "SEMANTIC_VERSION_LESS_THAN": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN; - break; - case "SEMANTIC_VERSION_NOT_EQUAL": - this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL; - break; - case "STRING_CONTAINS": - this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS; - break; - case "STRING_CONTAINS_REGEX": - this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS_REGEX; - break; - case "STRING_DOES_NOT_CONTAIN": - this.customSignalOperator = CustomSignalOperator.STRING_DOES_NOT_CONTAIN; - break; - case "STRING_EXACTLY_MATCHES": - this.customSignalOperator = CustomSignalOperator.STRING_EXACTLY_MATCHES; - break; - default: - this.customSignalOperator = CustomSignalOperator.UNSPECIFIED; - } - checkArgument( - this.customSignalOperator != CustomSignalOperator.UNSPECIFIED, - "Custom signal operator passed is invalid"); - } - - @NonNull - String getCustomSignalKey() { - return customSignalKey; - } - - @NonNull - CustomSignalOperator getCustomSignalOperator() { - return customSignalOperator; - } - - @NonNull - List getTargetCustomSignalValues() { - return new ArrayList<>(targetCustomSignalValues); - } - - CustomSignalConditionResponse toCustomConditonResponse() { - CustomSignalConditionResponse customSignalConditionResponse = - new CustomSignalConditionResponse(); - customSignalConditionResponse.setKey(this.customSignalKey); - customSignalConditionResponse.setOperator(this.customSignalOperator.getOperator()); - customSignalConditionResponse.setTargetValues(this.targetCustomSignalValues); - return customSignalConditionResponse; - } -} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java deleted file mode 100644 index 093d19cd0..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.remoteconfig; - -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.common.base.Strings; -import com.google.firebase.internal.NonNull; - -enum CustomSignalOperator { - NUMERIC_EQUAL("NUMERIC_EQUAL"), - NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), - NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), - NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), - NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), - NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), - SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), - SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), - SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), - SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), - SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), - SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), - STRING_CONTAINS("STRING_CONTAINS"), - STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), - STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), - STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), - UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); - - private final String operator; - - CustomSignalOperator(@NonNull String operator) { - checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); - this.operator = operator; - } - - @NonNull - String getOperator() { - return operator; - } -} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java deleted file mode 100644 index a90205449..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java +++ /dev/null @@ -1,50 +0,0 @@ - -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.remoteconfig; - -import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.Nullable; -import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.MicroPercentRangeResponse; - -class MicroPercentRange { - private final int microPercentLowerBound; - private final int microPercentUpperBound; - - public MicroPercentRange(@Nullable Integer microPercentLowerBound, - @Nullable Integer microPercentUpperBound) { - this.microPercentLowerBound = microPercentLowerBound != null ? microPercentLowerBound : 0; - this.microPercentUpperBound = microPercentUpperBound != null ? microPercentUpperBound : 0; - } - - @NonNull - int getMicroPercentLowerBound() { - return microPercentLowerBound; - } - - @NonNull - int getMicroPercentUpperBound() { - return microPercentUpperBound; - } - - MicroPercentRangeResponse toMicroPercentRangeResponse() { - MicroPercentRangeResponse microPercentRangeResponse = new MicroPercentRangeResponse(); - microPercentRangeResponse.setMicroPercentLowerBound(this.microPercentLowerBound); - microPercentRangeResponse.setMicroPercentUpperBound(this.microPercentUpperBound); - return microPercentRangeResponse; - } -} diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 9084c84cf..838e9e4cf 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -26,8 +26,6 @@ class OneOfCondition { private OrCondition orCondition; private AndCondition andCondition; - private PercentCondition percent; - private CustomSignalCondition customSignal; private String trueValue; private String falseValue; @@ -38,21 +36,12 @@ class OneOfCondition { if (oneOfconditionResponse.getAndCondition() != null) { this.andCondition = new AndCondition(oneOfconditionResponse.getAndCondition()); } - if (oneOfconditionResponse.getPercentCondition() != null) { - this.percent = new PercentCondition(oneOfconditionResponse.getPercentCondition()); - } - if (oneOfconditionResponse.getCustomSignalCondition() != null) { - this.customSignal = - new CustomSignalCondition(oneOfconditionResponse.getCustomSignalCondition()); - } } @VisibleForTesting OneOfCondition() { this.orCondition = null; this.andCondition = null; - this.percent = null; - this.customSignal = null; this.trueValue = null; this.falseValue = null; } @@ -77,16 +66,6 @@ String isFalse() { return falseValue; } - @Nullable - PercentCondition getPercent() { - return percent; - } - - @Nullable - CustomSignalCondition getCustomSignal() { - return customSignal; - } - OneOfCondition setOrCondition(@NonNull OrCondition orCondition) { checkNotNull(orCondition, "`Or` condition cannot be set to null."); this.orCondition = orCondition; @@ -99,18 +78,6 @@ OneOfCondition setAndCondition(@NonNull AndCondition andCondition) { return this; } - OneOfCondition setPercent(@NonNull PercentCondition percent) { - checkNotNull(percent, "`Percent` condition cannot be set to null."); - this.percent = percent; - return this; - } - - OneOfCondition setCustomSignal(@NonNull CustomSignalCondition customSignal) { - checkNotNull(customSignal, "`Custom signal` condition cannot be set to null."); - this.customSignal = customSignal; - return this; - } - OneOfCondition setTrue() { this.trueValue = "true"; return this; @@ -129,12 +96,6 @@ OneOfConditionResponse toOneOfConditionResponse() { if (this.orCondition != null) { oneOfConditionResponse.setOrCondition(this.orCondition.toOrConditionResponse()); } - if (this.percent != null) { - oneOfConditionResponse.setPercentCondition(this.percent.toPercentConditionResponse()); - } - if (this.customSignal != null) { - oneOfConditionResponse.setCustomSignalCondition(this.customSignal.toCustomConditonResponse()); - } return oneOfConditionResponse; } } \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java deleted file mode 100644 index a692d4f34..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java +++ /dev/null @@ -1,164 +0,0 @@ - -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.remoteconfig; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.base.Strings; -import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.Nullable; -import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.PercentConditionResponse; - -/** Represents a condition that compares the instance pseudo-random percentile to a given limit. */ -public final class PercentCondition { - private int microPercent; - private MicroPercentRange microPercentRange; - private final PercentConditionOperator percentConditionOperator; - private final String seed; - - /** - * Create a percent condition for operator BETWEEN. - * - * @param microPercent The limit of percentiles to target in micro-percents when using the - * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 and 100000000]. - * @param percentConditionOperator The choice of percent operator to determine how to compare - * targets to percent(s). - * @param seed The seed used when evaluating the hash function to map an instance to a value in - * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII - * characters [-_.0-9a-zA-Z].The string is case-sensitive. - */ - PercentCondition( - @Nullable Integer microPercent, - @NonNull PercentConditionOperator percentConditionOperator, - @NonNull String seed) { - checkNotNull(percentConditionOperator, "Percentage operator must not be null."); - checkArgument(!Strings.isNullOrEmpty(seed), "Seed must not be null or empty."); - this.microPercent = microPercent != null ? microPercent : 0; - this.percentConditionOperator = percentConditionOperator; - this.seed = seed; - } - - /** - * Create a percent condition for operators GREATER_THAN and LESS_OR_EQUAL. - * - * @param microPercentRange The micro-percent interval to be used with the BETWEEN operator. - * @param percentConditionOperator The choice of percent operator to determine how to compare - * targets to percent(s). - * @param seed The seed used when evaluating the hash function to map an instance to a value in - * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII - * characters [-_.0-9a-zA-Z].The string is case-sensitive. - */ - PercentCondition( - @NonNull MicroPercentRange microPercentRange, - @NonNull PercentConditionOperator percentConditionOperator, - String seed) { - checkNotNull(microPercentRange, "Percent range must not be null."); - checkNotNull(percentConditionOperator, "Percentage operator must not be null."); - this.microPercentRange = microPercentRange; - this.percentConditionOperator = percentConditionOperator; - this.seed = seed; - } - - /** - * Creates a new {@link PercentCondition} from API response. - * - * @param percentCondition the conditions obtained from server call. - */ - PercentCondition(PercentConditionResponse percentCondition) { - checkArgument( - !Strings.isNullOrEmpty(percentCondition.getSeed()), "Seed must not be empty or null"); - this.microPercent = percentCondition.getMicroPercent(); - this.seed = percentCondition.getSeed(); - switch (percentCondition.getPercentOperator()) { - case "BETWEEN": - this.percentConditionOperator = PercentConditionOperator.BETWEEN; - break; - case "GREATER_THAN": - this.percentConditionOperator = PercentConditionOperator.GREATER_THAN; - break; - case "LESS_OR_EQUAL": - this.percentConditionOperator = PercentConditionOperator.LESS_OR_EQUAL; - break; - default: - this.percentConditionOperator = PercentConditionOperator.UNSPECIFIED; - } - checkArgument( - this.percentConditionOperator != PercentConditionOperator.UNSPECIFIED, - "Percentage operator is invalid"); - if (percentCondition.getMicroPercentRange() != null) { - this.microPercentRange = - new MicroPercentRange( - percentCondition.getMicroPercentRange().getMicroPercentLowerBound(), - percentCondition.getMicroPercentRange().getMicroPercentUpperBound()); - } - } - - /** - * Gets the limit of percentiles to target in micro-percents when using the LESS_OR_EQUAL and - * GREATER_THAN operators. The value must be in the range [0 and 100000000]. - * - * @return micro percent. - */ - @Nullable - public int getMicroPercent() { - return microPercent; - } - - /** - * Gets micro-percent interval to be used with the BETWEEN operator. - * - * @return micro percent range. - */ - @Nullable - public MicroPercentRange getMicroPercentRange() { - return microPercentRange; - } - - /** - * Gets choice of percent operator to determine how to compare targets to percent(s). - * - * @return operator. - */ - @NonNull - public PercentConditionOperator getPercentConditionOperator() { - return percentConditionOperator; - } - - /** - * The seed used when evaluating the hash function to map an instance to a value in the hash - * space. This is a string which can have 0 - 32 characters and can contain ASCII characters - * [-_.0-9a-zA-Z].The string is case-sensitive. - * - * @return seed. - */ - @NonNull - public String getSeed() { - return seed; - } - - PercentConditionResponse toPercentConditionResponse() { - PercentConditionResponse percentConditionResponse = new PercentConditionResponse(); - percentConditionResponse.setMicroPercent(this.microPercent); - percentConditionResponse.setMicroPercentRange( - this.microPercentRange.toMicroPercentRangeResponse()); - percentConditionResponse.setPercentOperator(this.percentConditionOperator.getOperator()); - percentConditionResponse.setSeed(this.seed); - return percentConditionResponse; - } -} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java deleted file mode 100644 index 72240b2c9..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java +++ /dev/null @@ -1,56 +0,0 @@ - -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.remoteconfig; - -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.common.base.Strings; -import com.google.firebase.internal.NonNull; - -/** - * Defines supported operators for percent conditions. - */ -public enum PercentConditionOperator { - BETWEEN("BETWEEN"), - GREATER_THAN("GREATER_THAN"), - LESS_OR_EQUAL("LESS_OR_EQUAL"), - UNSPECIFIED("PERCENT_OPERATOR_UNSPECIFIED"); - - private final String operator; - - /** - * Creates percent condition operator. - * - * @param operator The choice of percent operator to determine how to compare targets to - * percent(s). - */ - PercentConditionOperator(@NonNull String operator) { - checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); - this.operator = operator; - } - - /** - * Gets percent condition operator. - * - * @return operator. - */ - @NonNull - public String getOperator() { - return operator; - } -} diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index edc52a19d..b6805f6c5 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpMethods; From 46ddda9b317621e0e6b79b84ea468919b7c3f1ea Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Wed, 18 Jun 2025 13:07:21 +0530 Subject: [PATCH 03/13] fixed build errors --- .../firebase/remoteconfig/{OrCondtion.java => OrCondition.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/google/firebase/remoteconfig/{OrCondtion.java => OrCondition.java} (100%) diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondtion.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java similarity index 100% rename from src/main/java/com/google/firebase/remoteconfig/OrCondtion.java rename to src/main/java/com/google/firebase/remoteconfig/OrCondition.java From fbd0b212cf199ab5119156a0874826cb9a820cb9 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 11:08:24 +0530 Subject: [PATCH 04/13] fix related to comment changes --- .../firebase/remoteconfig/AndCondition.java | 2 +- .../FirebaseRemoteConfigClient.java | 3 +- .../FirebaseRemoteConfigClientImpl.java | 2 +- .../firebase/remoteconfig/KeysAndValues.java | 2 +- .../firebase/remoteconfig/OneOfCondition.java | 4 +- .../firebase/remoteconfig/OrCondition.java | 2 +- .../remoteconfig/ServerCondition.java | 4 +- .../firebase/remoteconfig/ServerConfig.java | 4 +- .../firebase/remoteconfig/ServerTemplate.java | 14 +-- .../remoteconfig/ServerTemplateData.java | 2 +- .../remoteconfig/ServerTemplateImpl.java | 86 +------------------ .../google/firebase/remoteconfig/Value.java | 4 +- .../firebase/remoteconfig/ValueSource.java | 2 +- .../internal/ServerTemplateResponse.java | 4 +- 14 files changed, 21 insertions(+), 114 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java index d64e7b2db..d91b1da75 100644 --- a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java index d648fe750..8b7a93ce5 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java @@ -42,5 +42,4 @@ ListVersionsResponse listVersions( ListVersionsOptions options) throws FirebaseRemoteConfigException; String getServerTemplate() throws FirebaseRemoteConfigException; - -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index b552c5013..3fce3d02b 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -300,4 +300,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) { return new RemoteConfigServiceErrorResponse(); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java index 36fdac361..61c7d23f6 100644 --- a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 838e9e4cf..5d02864c9 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,4 +98,4 @@ OneOfConditionResponse toOneOfConditionResponse() { } return oneOfConditionResponse; } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java index e3a2c1398..5b7f932ee 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java index fa8eb32c5..0fac8ec98 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,4 +86,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, serverCondition); } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java index a498fb994..8540578b0 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,4 +99,4 @@ private Value getValue(String key) { } return new Value(ValueSource.STATIC); } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index 2ea8b8289..f5df3bbf2 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,20 +27,10 @@ public interface Builder { ServerTemplate build(); } - /** - * Proccess the template data with a condition evaluator - * based on the provided context. - */ - ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException; - /** - * Proccess the template data without context. - */ - ServerConfig evaluate() throws FirebaseRemoteConfigException; - /** * Fetches and caches the current active version of the project. */ ApiFuture load() throws FirebaseRemoteConfigException; String toJson(); -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java index 344046862..bec1ec001 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java index 1afe4037c..71b4d96c0 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,27 +19,18 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; -import com.google.common.collect.ImmutableMap; -import com.google.firebase.ErrorCode; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public final class ServerTemplateImpl implements ServerTemplate { private final KeysAndValues defaultConfig; private FirebaseRemoteConfigClient client; private ServerTemplateData cache; private final AtomicReference cachedTemplate; // Added field for cached template - private static final Logger logger = LoggerFactory.getLogger(ServerTemplate.class); - + public static class Builder implements ServerTemplate.Builder { private KeysAndValues defaultConfig; private String cachedTemplate; @@ -74,25 +65,10 @@ private ServerTemplateImpl(Builder builder) { try { this.cache = ServerTemplateData.fromJSON(this.cachedTemplate.get()); } catch (FirebaseRemoteConfigException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } - //TODO : Implementation of Evaluate will be in another PR - @Override - public ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException { - - Map configValues = new HashMap<>(); - return new ServerConfig(configValues); - } - - @Override - public ServerConfig evaluate() throws FirebaseRemoteConfigException { - KeysAndValues context = new KeysAndValues.Builder().build(); - return evaluate(context); - } - @Override public ApiFuture load() throws FirebaseRemoteConfigException { String serverTemplate = client.getServerTemplate(); @@ -115,62 +91,4 @@ public String toJson() { Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(this.cache); } - - private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, - ImmutableMap parameters, Map configValues) { - // Overlays config Value objects derived by evaluating the template. - for (String parameterName : parameters.keySet()) { - Parameter parameter = parameters.get(parameterName); - if (parameter == null) { - logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); - continue; - } - - ImmutableMap conditionalValues = ImmutableMap.copyOf( - parameter.getConditionalValues()); - ParameterValue derivedValue = null; - - // Iterates in order over condition list. If there is a value associated - // with a condition, this checks if the condition is true. - for (String conditionName : evaluatedCondition.keySet()) { - boolean conditionEvaluation = evaluatedCondition.get(conditionName); - if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { - derivedValue = conditionalValues.get(conditionName); - break; - } - } - - if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { - logger.warn( - String.format("Derived value found for %s but parameter is set to use in app default.", - parameterName)); - continue; - } - - if (derivedValue != null) { - String parameterValue = derivedValue.toParameterValueResponse().getValue(); - Value value = new Value(ValueSource.REMOTE, parameterValue); - configValues.put(parameterName, value); - continue; - } - - ParameterValue defaultValue = parameter.getDefaultValue(); - if (defaultValue == null) { - logger.warn(String.format("Default parameter value for %s is not set.", - parameterName)); - continue; - } - - ParameterValueResponse defaultValueResponse = defaultValue.toParameterValueResponse(); - if (defaultValueResponse != null && defaultValueResponse.isUseInAppDefault()) { - logger.info(String.format("Default value for %s is set to use in app default.", - parameterName)); - continue; - } - - String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); - Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); - configValues.put(parameterName, value); - } - } } diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java index aecc98c1a..d98641c7e 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Value.java +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -132,4 +132,4 @@ long asLong() { ValueSource getSource() { return source; } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java index 2f7f9487a..460f385c3 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java +++ b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java @@ -1,6 +1,6 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java index cf12c00a2..5016a6e50 100644 --- a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -317,4 +317,4 @@ public MicroPercentRangeResponse setMicroPercentUpperBound(int microPercentUpper return this; } } -} \ No newline at end of file +} From dd9c4465b8917796bb22312614d3ee11194cbf19 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 12:48:48 +0530 Subject: [PATCH 05/13] add whitespace --- src/main/java/com/google/firebase/remoteconfig/AndCondition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java index d91b1da75..3be10560a 100644 --- a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -61,3 +61,4 @@ AndConditionResponse toAndConditionResponse() { .collect(Collectors.toList())); } } + From 8ac4bca97248c230ce9cc064b8cb4818db3d90e5 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 12:57:24 +0530 Subject: [PATCH 06/13] added whitespace at end of package for maintaining clean and consistent code --- .../com/google/firebase/remoteconfig/FirebaseRemoteConfig.java | 1 + .../firebase/remoteconfig/FirebaseRemoteConfigClient.java | 1 + .../firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java | 1 + .../java/com/google/firebase/remoteconfig/KeysAndValues.java | 1 + .../java/com/google/firebase/remoteconfig/OneOfCondition.java | 1 + src/main/java/com/google/firebase/remoteconfig/OrCondition.java | 1 + .../java/com/google/firebase/remoteconfig/ServerCondition.java | 1 + .../java/com/google/firebase/remoteconfig/ServerConfig.java | 1 + .../java/com/google/firebase/remoteconfig/ServerTemplate.java | 1 + .../com/google/firebase/remoteconfig/ServerTemplateData.java | 1 + .../com/google/firebase/remoteconfig/ServerTemplateImpl.java | 1 + src/main/java/com/google/firebase/remoteconfig/Value.java | 1 + src/main/java/com/google/firebase/remoteconfig/ValueSource.java | 1 + .../firebase/remoteconfig/internal/ServerTemplateResponse.java | 1 + .../remoteconfig/FirebaseRemoteConfigClientImplTest.java | 1 + .../google/firebase/remoteconfig/FirebaseRemoteConfigTest.java | 2 +- .../google/firebase/remoteconfig/MockRemoteConfigClient.java | 2 +- 17 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index b1786b478..e3edce4e4 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -480,3 +480,4 @@ public void destroy() { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java index 8b7a93ce5..2143d07d1 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java @@ -43,3 +43,4 @@ ListVersionsResponse listVersions( String getServerTemplate() throws FirebaseRemoteConfigException; } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 3fce3d02b..3f3fc6747 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -301,3 +301,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java index 61c7d23f6..97064d96a 100644 --- a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -133,3 +133,4 @@ public KeysAndValues build() { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 5d02864c9..255cee565 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -99,3 +99,4 @@ OneOfConditionResponse toOneOfConditionResponse() { return oneOfConditionResponse; } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java index 5b7f932ee..4d258f23b 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -58,3 +58,4 @@ OrConditionResponse toOrConditionResponse() { .collect(Collectors.toList())); } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java index 0fac8ec98..f16aeffc8 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java @@ -87,3 +87,4 @@ public int hashCode() { return Objects.hash(name, serverCondition); } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java index 8540578b0..9e063009f 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -100,3 +100,4 @@ private Value getValue(String key) { return new Value(ValueSource.STATIC); } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index f5df3bbf2..f16992744 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -34,3 +34,4 @@ public interface Builder { String toJson(); } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java index bec1ec001..34674855f 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -213,3 +213,4 @@ public int hashCode() { return Objects.hash(etag, parameters, serverConditions, parameterGroups, version); } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java index 71b4d96c0..81c71e898 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java @@ -92,3 +92,4 @@ public String toJson() { return gson.toJson(this.cache); } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java index d98641c7e..7f04aefeb 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Value.java +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -133,3 +133,4 @@ ValueSource getSource() { return source; } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java index 460f385c3..c870e8514 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java +++ b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java @@ -28,3 +28,4 @@ public enum ValueSource { REMOTE, DEFAULT } + diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java index 5016a6e50..87f624a78 100644 --- a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -318,3 +318,4 @@ public MicroPercentRangeResponse setMicroPercentUpperBound(int microPercentUpper } } } + diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index b6805f6c5..fb62699dd 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -1238,3 +1238,4 @@ private void checkExceptionFromHttpResponse( assertTrue(request.getUrl().startsWith("https://firebaseremoteconfig.googleapis.com")); } } + diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index 40d569294..25e577564 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -674,4 +674,4 @@ public void testGetServerTemplateAsyncFailure() throws InterruptedException { assertSame(TEST_EXCEPTION, e.getCause()); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 1ff31ebb8..ad1776535 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -101,4 +101,4 @@ public ListVersionsResponse listVersions( } return listVersionsResponse; } -} \ No newline at end of file +} From d85247b17c7f101c1ed913b84210bf4baf6f1964 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:14:05 +0530 Subject: [PATCH 07/13] fix issue in lint --- .../FirebaseRemoteConfigClientImpl.java | 116 ++++++++---------- 1 file changed, 52 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 3f3fc6747..5e36d5194 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -40,30 +40,30 @@ import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse; import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse; + import java.io.IOException; import java.util.List; import java.util.Map; -/** A helper class for interacting with Firebase Remote Config service. */ +/** + * A helper class for interacting with Firebase Remote Config service. + */ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient { - private static final String REMOTE_CONFIG_URL = - "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; + private static final String REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; private static final String SERVER_REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/firebase-server/serverRemoteConfig"; private static final Map COMMON_HEADERS = - ImmutableMap.of( - "X-Firebase-Client", - "fire-admin-java/" + SdkUtils.getVersion(), - // There is a known issue in which the ETag is not properly returned in cases - // where the request does not specify a compression type. Currently, it is - // required to include the header `Accept-Encoding: gzip` or equivalent in all - // requests. - // https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates - "Accept-Encoding", - "gzip"); + ImmutableMap.of( + "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion(), + // There is a known issue in which the ETag is not properly returned in cases + // where the request does not specify a compression type. Currently, it is + // required to include the header `Accept-Encoding: gzip` or equivalent in all + // requests. https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates + "Accept-Encoding", "gzip" + ); private final String remoteConfigUrl; private final String serverRemoteConfigUrl; @@ -79,8 +79,7 @@ private FirebaseRemoteConfigClientImpl(Builder builder) { this.jsonFactory = checkNotNull(builder.jsonFactory); HttpResponseInterceptor responseInterceptor = builder.responseInterceptor; RemoteConfigErrorHandler errorHandler = new RemoteConfigErrorHandler(this.jsonFactory); - this.httpClient = - new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler) + this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler) .setInterceptor(responseInterceptor); } @@ -106,8 +105,8 @@ JsonFactory getJsonFactory() { @Override public Template getTemplate() throws FirebaseRemoteConfigException { - HttpRequestInfo request = - HttpRequestInfo.buildGetRequest(remoteConfigUrl).addAllHeaders(COMMON_HEADERS); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl) + .addAllHeaders(COMMON_HEADERS); IncomingHttpResponse response = httpClient.send(request); TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class); Template template = new Template(templateResponse); @@ -127,13 +126,11 @@ public String getServerTemplate() throws FirebaseRemoteConfigException { } @Override - public Template getTemplateAtVersion(@NonNull String versionNumber) - throws FirebaseRemoteConfigException { - checkArgument( - RemoteConfigUtil.isValidVersionNumber(versionNumber), - "Version number must be a non-empty string in int64 format."); - HttpRequestInfo request = - HttpRequestInfo.buildGetRequest(remoteConfigUrl) + public Template getTemplateAtVersion( + @NonNull String versionNumber) throws FirebaseRemoteConfigException { + checkArgument(RemoteConfigUtil.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); @@ -143,15 +140,11 @@ public Template getTemplateAtVersion(@NonNull String versionNumber) } @Override - public Template publishTemplate( - @NonNull Template template, boolean validateOnly, boolean forcePublish) - throws FirebaseRemoteConfigException { + 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(false))) + HttpRequestInfo request = HttpRequestInfo.buildRequest("PUT", remoteConfigUrl, + new JsonHttpContent(jsonFactory, template.toTemplateResponse(false))) .addAllHeaders(COMMON_HEADERS) .addHeader("If-Match", forcePublish ? "*" : template.getETag()); if (validateOnly) { @@ -171,12 +164,11 @@ public Template publishTemplate( @Override public Template rollback(@NonNull String versionNumber) throws FirebaseRemoteConfigException { - checkArgument( - RemoteConfigUtil.isValidVersionNumber(versionNumber), - "Version number must be a non-empty string in int64 format."); + checkArgument(RemoteConfigUtil.isValidVersionNumber(versionNumber), + "Version number must be a non-empty string in int64 format."); Map content = ImmutableMap.of("versionNumber", versionNumber); - HttpRequestInfo request = - HttpRequestInfo.buildJsonPostRequest(remoteConfigUrl + ":rollback", content) + HttpRequestInfo request = HttpRequestInfo + .buildJsonPostRequest(remoteConfigUrl + ":rollback", content) .addAllHeaders(COMMON_HEADERS); IncomingHttpResponse response = httpClient.send(request); TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class); @@ -185,10 +177,9 @@ public Template rollback(@NonNull String versionNumber) throws FirebaseRemoteCon } @Override - public TemplateResponse.ListVersionsResponse listVersions(ListVersionsOptions options) - throws FirebaseRemoteConfigException { - HttpRequestInfo request = - HttpRequestInfo.buildGetRequest(remoteConfigUrl + ":listVersions") + public TemplateResponse.ListVersionsResponse listVersions( + ListVersionsOptions options) throws FirebaseRemoteConfigException { + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl + ":listVersions") .addAllHeaders(COMMON_HEADERS); if (options != null) { request.addAllParameters(options.wrapForTransport()); @@ -198,30 +189,27 @@ public TemplateResponse.ListVersionsResponse listVersions(ListVersionsOptions op private String getETag(IncomingHttpResponse response) { List etagList = (List) response.getHeaders().get("etag"); - checkState( - etagList != null && !etagList.isEmpty(), - "ETag header is not available in the server response."); - + checkState(etagList != null && !etagList.isEmpty(), + "ETag header is not available in the server response."); String etag = etagList.get(0); - checkState( - !Strings.isNullOrEmpty(etag), "ETag header is not available in the server response."); + checkState(!Strings.isNullOrEmpty(etag), + "ETag header is not available in the server response."); return etag; } static FirebaseRemoteConfigClientImpl fromApp(FirebaseApp app) { String projectId = ImplFirebaseTrampolines.getProjectId(app); - checkArgument( - !Strings.isNullOrEmpty(projectId), - "Project ID is required to access Remote Config service. Use a service " - + "account credential or set the project ID explicitly via FirebaseOptions. " - + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " - + "environment variable."); + checkArgument(!Strings.isNullOrEmpty(projectId), + "Project ID is required to access Remote Config service. Use a service " + + "account credential or set the project ID explicitly via FirebaseOptions. " + + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " + + "environment variable."); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId(projectId) - .setRequestFactory(ApiClientUtils.newAuthorizedRequestFactory(app)) - .setJsonFactory(app.getOptions().getJsonFactory()) - .build(); + .setProjectId(projectId) + .setRequestFactory(ApiClientUtils.newAuthorizedRequestFactory(app)) + .setJsonFactory(app.getOptions().getJsonFactory()) + .build(); } static Builder builder() { @@ -235,7 +223,7 @@ static final class Builder { private JsonFactory jsonFactory; private HttpResponseInterceptor responseInterceptor; - private Builder() {} + private Builder() { } Builder setProjectId(String projectId) { this.projectId = projectId; @@ -252,7 +240,8 @@ Builder setJsonFactory(JsonFactory jsonFactory) { return this; } - Builder setResponseInterceptor(HttpResponseInterceptor responseInterceptor) { + Builder setResponseInterceptor( + HttpResponseInterceptor responseInterceptor) { this.responseInterceptor = responseInterceptor; return this; } @@ -263,7 +252,7 @@ FirebaseRemoteConfigClientImpl build() { } private static class RemoteConfigErrorHandler - extends AbstractPlatformErrorHandler { + extends AbstractPlatformErrorHandler { private RemoteConfigErrorHandler(JsonFactory jsonFactory) { super(jsonFactory); @@ -274,7 +263,7 @@ protected FirebaseRemoteConfigException createException(FirebaseException base) String response = getResponse(base); RemoteConfigServiceErrorResponse parsed = safeParse(response); return FirebaseRemoteConfigException.withRemoteConfigErrorCode( - base, parsed.getRemoteConfigErrorCode()); + base, parsed.getRemoteConfigErrorCode()); } private String getResponse(FirebaseException base) { @@ -288,9 +277,8 @@ private String getResponse(FirebaseException base) { private RemoteConfigServiceErrorResponse safeParse(String response) { if (!Strings.isNullOrEmpty(response)) { try { - return jsonFactory - .createJsonParser(response) - .parseAndClose(RemoteConfigServiceErrorResponse.class); + return jsonFactory.createJsonParser(response) + .parseAndClose(RemoteConfigServiceErrorResponse.class); } catch (IOException ignore) { // Ignore any error that may occur while parsing the error response. The server // may have responded with a non-json payload. From c7d68eb5db0254102d19b4dfcbbed2731910232f Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:28:00 +0530 Subject: [PATCH 08/13] fix lint error --- .../FirebaseRemoteConfigClientImpl.java | 3 +- .../FirebaseRemoteConfigClientImplTest.java | 2 + .../FirebaseRemoteConfigTest.java | 79 +++++++++---------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 5e36d5194..d84abae84 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -191,6 +191,7 @@ private String getETag(IncomingHttpResponse response) { List etagList = (List) response.getHeaders().get("etag"); checkState(etagList != null && !etagList.isEmpty(), "ETag header is not available in the server response."); + String etag = etagList.get(0); checkState(!Strings.isNullOrEmpty(etag), "ETag header is not available in the server response."); @@ -263,7 +264,7 @@ protected FirebaseRemoteConfigException createException(FirebaseException base) String response = getResponse(base); RemoteConfigServiceErrorResponse parsed = safeParse(response); return FirebaseRemoteConfigException.withRemoteConfigErrorCode( - base, parsed.getRemoteConfigErrorCode()); + base, parsed.getRemoteConfigErrorCode()); } private String getResponse(FirebaseException base) { diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index fb62699dd..aea2dd1eb 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpMethods; @@ -41,6 +42,7 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.OutgoingHttpRequest; + import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.internal.SdkUtils; diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index 25e577564..66821abb8 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -27,8 +27,11 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; + import com.google.firebase.remoteconfig.internal.TemplateResponse; + import java.util.concurrent.ExecutionException; + import org.junit.After; import org.junit.Test; @@ -37,8 +40,7 @@ * */ public class FirebaseRemoteConfigTest { - private static final FirebaseOptions TEST_OPTIONS = - FirebaseOptions.builder() + private static final FirebaseOptions TEST_OPTIONS = FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); @@ -50,7 +52,7 @@ public class FirebaseRemoteConfigTest { + " \"parameterGroups\": {}\n" + "}"; private static final FirebaseRemoteConfigException TEST_EXCEPTION = - new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); + new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); @After public void tearDown() { @@ -84,8 +86,7 @@ public void testDefaultRemoteConfigClient() { assertTrue(client instanceof FirebaseRemoteConfigClientImpl); assertSame(client, remoteConfig.getRemoteConfigClient()); - String expectedUrl = - "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + String expectedUrl = "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getRemoteConfigUrl()); } @@ -121,16 +122,16 @@ public void testAppDelete() { @Test public void testRemoteConfigClientWithoutProjectId() { - FirebaseOptions options = - FirebaseOptions.builder().setCredentials(new MockGoogleCredentials("test-token")).build(); + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .build(); FirebaseApp.initializeApp(options); try { FirebaseRemoteConfig.getInstance(); fail("No error thrown for missing project ID"); } catch (IllegalArgumentException expected) { - String message = - "Project ID is required to access Remote Config service. Use a service " + String message = "Project ID is required to access Remote Config service. Use a service " + "account credential or set the project ID explicitly via FirebaseOptions. " + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " + "environment variable."; @@ -144,8 +145,8 @@ public void testRemoteConfigClientWithoutProjectId() { @Test public void testGetTemplate() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplate(); @@ -167,8 +168,8 @@ public void testGetTemplateFailure() { @Test public void testGetTemplateAsync() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAsync().get(); @@ -192,8 +193,8 @@ public void testGetTemplateAsyncFailure() throws InterruptedException { @Test public void testGetTemplateAtVersionWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion("64"); @@ -215,8 +216,8 @@ public void testGetTemplateAtVersionWithStringValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync("55").get(); @@ -238,8 +239,8 @@ public void testGetTemplateAtVersionAsyncWithStringValueFailure() throws Interru @Test public void testGetTemplateAtVersionWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion(64L); @@ -261,8 +262,8 @@ public void testGetTemplateAtVersionWithLongValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync(55L).get(); @@ -430,8 +431,8 @@ public void testForcePublishTemplateAsyncFailure() throws InterruptedException { @Test public void testRollbackWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback("64"); @@ -453,8 +454,8 @@ public void testRollbackWithStringValueFailure() { @Test public void testRollbackAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync("55").get(); @@ -476,8 +477,8 @@ public void testRollbackAsyncWithStringValueFailure() throws InterruptedExceptio @Test public void testRollbackWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback(64L); @@ -499,8 +500,8 @@ public void testRollbackWithLongValueFailure() { @Test public void testRollbackAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( + new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync(55L).get(); @@ -524,8 +525,7 @@ public void testRollbackAsyncWithLongValueFailure() throws InterruptedException @Test public void testListVersionsWithNoOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -548,8 +548,7 @@ public void testListVersionsWithNoOptionsFailure() { @Test public void testListVersionsAsyncWithNoOptions() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -572,13 +571,12 @@ public void testListVersionsAsyncWithNoOptionsFailure() throws InterruptedExcept @Test public void testListVersionsWithOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = - remoteConfig.listVersions(ListVersionsOptions.builder().build()); + ListVersionsPage listVersionsPage = remoteConfig.listVersions( + ListVersionsOptions.builder().build()); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -597,13 +595,12 @@ public void testListVersionsWithOptionsFailure() { @Test public void testListVersionsAsyncWithOptions() throws Exception { - MockRemoteConfigClient client = - MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = - remoteConfig.listVersionsAsync(ListVersionsOptions.builder().build()).get(); + ListVersionsPage listVersionsPage = remoteConfig.listVersionsAsync( + ListVersionsOptions.builder().build()).get(); assertEquals("token", listVersionsPage.getNextPageToken()); } From 5e80659325e4bf4600a56ccace52c52c638e6d12 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:30:04 +0530 Subject: [PATCH 09/13] fix lint errors --- .../google/firebase/remoteconfig/MockRemoteConfigClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index ad1776535..1c08d6e4f 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -36,7 +36,7 @@ private MockRemoteConfigClient(Template resultTemplate, } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { - return new MockRemoteConfigClient(resultTemplate,null, null, null); + return new MockRemoteConfigClient(resultTemplate, null, null, null); } static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { From 203013d1a3bdb548f48049f2575912a090b35db0 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:31:20 +0530 Subject: [PATCH 10/13] fixed formatting --- .../google/firebase/remoteconfig/MockRemoteConfigClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 1c08d6e4f..826be00d0 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -45,11 +45,11 @@ static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null,null, listVersionsResponse, null); + return new MockRemoteConfigClient(null, null, listVersionsResponse, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null,null, null, exception); + return new MockRemoteConfigClient(null, null, null, exception); } @Override From 9d9cf0d89b38c9d5fd95ef15328c9613c5e2c135 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:37:34 +0530 Subject: [PATCH 11/13] fix lint error --- .../remoteconfig/MockRemoteConfigClient.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 826be00d0..b1ae6785e 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -21,18 +21,18 @@ public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{ private final Template resultTemplate; - private final String resultServerTemplate; private final FirebaseRemoteConfigException exception; private final ListVersionsResponse listVersionsResponse; + private final String resultServerTemplate; private MockRemoteConfigClient(Template resultTemplate, - String resultServerTemplate, ListVersionsResponse listVersionsResponse, - FirebaseRemoteConfigException exception) { + FirebaseRemoteConfigException exception, + String resultServerTemplate) { this.resultTemplate = resultTemplate; - this.resultServerTemplate = resultServerTemplate; this.listVersionsResponse = listVersionsResponse; this.exception = exception; + this.resultServerTemplate = resultServerTemplate; } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { @@ -40,16 +40,16 @@ static MockRemoteConfigClient fromTemplate(Template resultTemplate) { } static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { - return new MockRemoteConfigClient(null, resultServerTemplate,null, null); + return new MockRemoteConfigClient(null, null, null, resultServerTemplate); } static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null, null, listVersionsResponse, null); + return new MockRemoteConfigClient(null, listVersionsResponse, null, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null, null, null, exception); + return new MockRemoteConfigClient(null, null, exception, null); } @Override From 7ab0056292cd81440350165592e5a95d6cabc37e Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:38:45 +0530 Subject: [PATCH 12/13] revert last change --- .../remoteconfig/MockRemoteConfigClient.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index b1ae6785e..1ff31ebb8 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -21,35 +21,35 @@ public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{ private final Template resultTemplate; + private final String resultServerTemplate; private final FirebaseRemoteConfigException exception; private final ListVersionsResponse listVersionsResponse; - private final String resultServerTemplate; private MockRemoteConfigClient(Template resultTemplate, + String resultServerTemplate, ListVersionsResponse listVersionsResponse, - FirebaseRemoteConfigException exception, - String resultServerTemplate) { + FirebaseRemoteConfigException exception) { this.resultTemplate = resultTemplate; + this.resultServerTemplate = resultServerTemplate; this.listVersionsResponse = listVersionsResponse; this.exception = exception; - this.resultServerTemplate = resultServerTemplate; } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { - return new MockRemoteConfigClient(resultTemplate, null, null, null); + return new MockRemoteConfigClient(resultTemplate,null, null, null); } static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { - return new MockRemoteConfigClient(null, null, null, resultServerTemplate); + return new MockRemoteConfigClient(null, resultServerTemplate,null, null); } static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null, listVersionsResponse, null, null); + return new MockRemoteConfigClient(null,null, listVersionsResponse, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null, null, exception, null); + return new MockRemoteConfigClient(null,null, null, exception); } @Override @@ -101,4 +101,4 @@ public ListVersionsResponse listVersions( } return listVersionsResponse; } -} +} \ No newline at end of file From 208bc58c03c3d090141792358e634720319bd5b3 Mon Sep 17 00:00:00 2001 From: Varun Rathore Date: Mon, 23 Jun 2025 13:39:40 +0530 Subject: [PATCH 13/13] add whitespace --- .../google/firebase/remoteconfig/MockRemoteConfigClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 1ff31ebb8..1e7c0d470 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -101,4 +101,5 @@ public ListVersionsResponse listVersions( } return listVersionsResponse; } -} \ No newline at end of file +} +