diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 20b5e3a61..038a700c3 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -36,6 +36,7 @@ import com.google.firebase.internal.HttpRequestInfo; import com.google.firebase.internal.SdkUtils; import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse; import java.io.IOException; import java.util.List; @@ -94,8 +95,9 @@ public RemoteConfigTemplate getTemplate() throws FirebaseRemoteConfigException { HttpRequestInfo request = HttpRequestInfo.buildGetRequest(remoteConfigUrl) .addAllHeaders(COMMON_HEADERS); IncomingHttpResponse response = httpClient.send(request); - RemoteConfigTemplate parsed = httpClient.parse(response, RemoteConfigTemplate.class); - return parsed.setETag(getETag(response)); + TemplateResponse templateResponse = httpClient.parse(response, TemplateResponse.class); + RemoteConfigTemplate template = templateResponse.toRemoteConfigTemplate(); + return template.setETag(getETag(response)); } private String getETag(IncomingHttpResponse response) { diff --git a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameter.java b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameter.java new file mode 100644 index 000000000..757c1437b --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameter.java @@ -0,0 +1,130 @@ +/* + * 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.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a Remote Config parameter that can be included in a {@link RemoteConfigTemplate}. + * At minimum, a default value or a conditional value must be present for the + * parameter to have any effect. + */ +public final class RemoteConfigParameter { + + private RemoteConfigParameterValue defaultValue; + private String description; + private Map conditionalValues; + + /** + * Creates a new {@link RemoteConfigParameter}. + */ + public RemoteConfigParameter() { + conditionalValues = new HashMap<>(); + } + + /** + * Gets the default value of the parameter. + * + * @return A {@link RemoteConfigParameterValue} instance or null. + */ + @Nullable + public RemoteConfigParameterValue getDefaultValue() { + return defaultValue; + } + + /** + * Gets the description of the parameter. + * + * @return The {@link String} description of the parameter or null. + */ + @Nullable + public String getDescription() { + return description; + } + + /** + * Gets the conditional values of the parameter. + * The condition name of the highest priority (the one listed first in the + * {@link RemoteConfigTemplate}'s conditions list) determines the value of this parameter. + * + * @return A non-null map of conditional values. + */ + @NonNull + public Map getConditionalValues() { + return conditionalValues; + } + + /** + * Sets the default value of the parameter. + * This is the value to set the parameter to, when none of the named conditions + * evaluate to true. + * + * @param value An {@link RemoteConfigParameterValue} instance. + * @return This {@link RemoteConfigParameter}. + */ + public RemoteConfigParameter setDefaultValue(@Nullable RemoteConfigParameterValue value) { + defaultValue = value; + return this; + } + + /** + * Sets the description of the parameter. + * Should not be over 100 characters and may contain any Unicode characters. + * + * @param description The description of the parameter. + * @return This {@link RemoteConfigParameter}. + */ + public RemoteConfigParameter setDescription(@Nullable String description) { + this.description = description; + return this; + } + + /** + * Sets the conditional values of the parameter. + * The condition name of the highest priority (the one listed first in the + * {@link RemoteConfigTemplate}'s conditions list) determines the value of this parameter. + * + * @param conditionalValues A non-null map of conditional values. + * @return This {@link RemoteConfigParameter}. + */ + public RemoteConfigParameter setConditionalValues( + @NonNull Map conditionalValues) { + checkNotNull(conditionalValues, "conditional values must not be null."); + this.conditionalValues = conditionalValues; + return this; + } + + ParameterResponse toParameterResponse() { + Map conditionalResponseValues = new HashMap<>(); + for (Map.Entry entry : conditionalValues.entrySet()) { + conditionalResponseValues.put(entry.getKey(), entry.getValue().toParameterValueResponse()); + } + ParameterValueResponse parameterValueResponse = (defaultValue == null) ? null : defaultValue + .toParameterValueResponse(); + return new ParameterResponse(parameterValueResponse, description, + conditionalResponseValues); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameterValue.java b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameterValue.java new file mode 100644 index 000000000..6a51ffd8f --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigParameterValue.java @@ -0,0 +1,84 @@ +/* + * 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.remoteconfig.internal.TemplateResponse.ParameterValueResponse; + +/** + * Represents a Remote Config parameter value that can be used in a {@link RemoteConfigTemplate}. + */ +public abstract class RemoteConfigParameterValue { + + /** + * Creates a new {@link RemoteConfigParameterValue.Explicit} instance with the given value. + * + * @param value The value of the {@link RemoteConfigParameterValue.Explicit}. + * @return A {@link RemoteConfigParameterValue.Explicit} instance. + */ + public static Explicit of(String value) { + return new Explicit(value); + } + + /** + * Creates a new {@link RemoteConfigParameterValue.InAppDefault} instance. + * + * @return A {@link RemoteConfigParameterValue.InAppDefault} instance. + */ + public static InAppDefault inAppDefault() { + return new InAppDefault(); + } + + abstract ParameterValueResponse toParameterValueResponse(); + + /** + * Represents an explicit Remote Config parameter value with a {@link String} value that the + * parameter is set to. + */ + public static final class Explicit extends RemoteConfigParameterValue { + + private final String value; + + private Explicit(String value) { + this.value = value; + } + + /** + * Gets the value of {@link RemoteConfigParameterValue.Explicit}. + * + * @return The {@link String} value. + */ + public String getValue() { + return this.value; + } + + @Override + ParameterValueResponse toParameterValueResponse() { + return ParameterValueResponse.ofValue(this.value); + } + } + + /** + * Represents an in app default parameter value. + */ + public static final class InAppDefault extends RemoteConfigParameterValue { + + @Override + ParameterValueResponse toParameterValueResponse() { + return ParameterValueResponse.ofInAppDefaultValue(); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigTemplate.java b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigTemplate.java index 632fb7777..143f5692b 100644 --- a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigTemplate.java @@ -16,19 +16,73 @@ package com.google.firebase.remoteconfig; -import com.google.api.client.util.Key; +import static com.google.common.base.Preconditions.checkNotNull; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.TemplateResponse; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a Remote Config template. + */ public final class RemoteConfigTemplate { - @Key("etag") private String etag; + private Map parameters; + /** + * Creates a new {@link RemoteConfigTemplate}. + */ + public RemoteConfigTemplate() { + parameters = new HashMap<>(); + } + + /** + * Gets the ETag of the template. + * + * @return The ETag of the template. + */ public String getETag() { return this.etag; } + /** + * Gets the map of parameters of the template. + * + * @return A non-null map of parameter keys to their optional default values and optional + * conditional values. + */ + @NonNull + public Map getParameters() { + return this.parameters; + } + + /** + * Sets the map of parameters of the template. + * + * @param parameters A non-null map of parameter keys to their optional default values and + * optional conditional values. + * @return This {@link RemoteConfigTemplate} instance. + */ + public RemoteConfigTemplate setParameters( + @NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } + RemoteConfigTemplate setETag(String etag) { this.etag = etag; return this; } + + TemplateResponse toTemplateResponse() { + Map parameterResponses = new HashMap<>(); + for (Map.Entry entry : parameters.entrySet()) { + parameterResponses.put(entry.getKey(), entry.getValue().toParameterResponse()); + } + return new TemplateResponse(parameterResponses); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java new file mode 100644 index 000000000..c1ae433da --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java @@ -0,0 +1,136 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.util.Key; +import com.google.firebase.database.annotations.Nullable; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.RemoteConfigParameter; +import com.google.firebase.remoteconfig.RemoteConfigParameterValue; +import com.google.firebase.remoteconfig.RemoteConfigTemplate; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The Data Transfer Object for parsing Remote Config template responses from the + * Remote Config service. + **/ +public final class TemplateResponse { + + @Key("parameters") + private Map parameters; + + public TemplateResponse() { + parameters = Collections.emptyMap(); + } + + public TemplateResponse(@NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + } + + public RemoteConfigTemplate toRemoteConfigTemplate() { + Map parameterPublicTypes = new HashMap<>(); + for (Map.Entry entry : parameters.entrySet()) { + parameterPublicTypes.put(entry.getKey(), entry.getValue().toRemoteConfigParameter()); + } + return new RemoteConfigTemplate().setParameters(parameterPublicTypes); + } + + /** + * The Data Transfer Object for parsing Remote Config parameter responses from the + * Remote Config service. + **/ + public static final class ParameterResponse { + + @Key("defaultValue") + private ParameterValueResponse defaultValue; + + @Key("description") + private String description; + + @Key("conditionalValues") + private Map conditionalValues; + + public ParameterResponse() { + conditionalValues = Collections.emptyMap(); + } + + public ParameterResponse(@Nullable ParameterValueResponse defaultValue, + @Nullable String description, + @NonNull Map conditionalValues) { + this.defaultValue = defaultValue; + this.description = description; + this.conditionalValues = checkNotNull(conditionalValues); + } + + public RemoteConfigParameter toRemoteConfigParameter() { + Map conditionalPublicValues = new HashMap<>(); + for (Map.Entry entry : conditionalValues.entrySet()) { + conditionalPublicValues + .put(entry.getKey(), entry.getValue().toRemoteConfigParameterValue()); + } + RemoteConfigParameterValue remoteConfigParameterValue = + (defaultValue == null) ? null : defaultValue.toRemoteConfigParameterValue(); + return new RemoteConfigParameter() + .setDefaultValue(remoteConfigParameterValue) + .setDescription(description) + .setConditionalValues(conditionalPublicValues); + } + } + + /** + * The Data Transfer Object for parsing Remote Config parameter value responses from the + * Remote Config service. + **/ + public static final class ParameterValueResponse { + + @Key("value") + private String value; + + @Key("useInAppDefault") + private Boolean inAppDefaultValue; + + public ParameterValueResponse() { + } + + private ParameterValueResponse(String value, Boolean inAppDefaultValue) { + this.value = value; + this.inAppDefaultValue = inAppDefaultValue; + } + + public static ParameterValueResponse ofValue(String value) { + return new ParameterValueResponse(value, null); + } + + public static ParameterValueResponse ofInAppDefaultValue() { + return new ParameterValueResponse(null, true); + } + + public RemoteConfigParameterValue toRemoteConfigParameterValue() { + if (this.inAppDefaultValue != null && this.inAppDefaultValue) { + return RemoteConfigParameterValue.inAppDefault(); + } + return RemoteConfigParameterValue.of(this.value); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/package-info.java b/src/main/java/com/google/firebase/remoteconfig/internal/package-info.java new file mode 100644 index 000000000..50a2768e9 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * @hide + */ +package com.google.firebase.remoteconfig.internal; diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index 8b6e94ce1..8ee5fb595 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -63,7 +63,8 @@ public class FirebaseRemoteConfigClientImplTest { 404, ErrorCode.NOT_FOUND, 500, ErrorCode.INTERNAL); - private static final String MOCK_TEMPLATE_RESPONSE = "{\"conditions\": [], \"parameters\": {}}"; + private static final String MOCK_TEMPLATE_RESPONSE = TestUtils + .loadResource("getRemoteConfig.json"); private static final String TEST_ETAG = "etag-123456789012-1"; @@ -86,6 +87,37 @@ public void testGetTemplate() throws Exception { RemoteConfigTemplate template = client.getTemplate(); assertEquals(TEST_ETAG, template.getETag()); + Map parameters = template.getParameters(); + assertEquals(2, parameters.size()); + assertTrue(parameters.containsKey("welcome_message_text")); + RemoteConfigParameter welcomeMessageParameter = parameters.get("welcome_message_text"); + assertEquals("text for welcome message!", welcomeMessageParameter.getDescription()); + RemoteConfigParameterValue.Explicit explicitDefaultValue = + (RemoteConfigParameterValue.Explicit) welcomeMessageParameter.getDefaultValue(); + assertEquals("welcome to app", explicitDefaultValue.getValue()); + Map conditionalValues = welcomeMessageParameter + .getConditionalValues(); + assertEquals(1, conditionalValues.size()); + assertTrue(conditionalValues.containsKey("ios_en")); + RemoteConfigParameterValue.Explicit value = + (RemoteConfigParameterValue.Explicit) conditionalValues.get("ios_en"); + assertEquals("welcome to app en", value.getValue()); + assertTrue(parameters.containsKey("header_text")); + RemoteConfigParameter headerParameter = parameters.get("header_text"); + assertTrue( + headerParameter.getDefaultValue() instanceof RemoteConfigParameterValue.InAppDefault); + checkGetRequestHeader(interceptor.getLastRequest()); + } + + @Test + public void testGetTemplateWithEmptyTemplateResponse() throws Exception { + response.addHeader("etag", TEST_ETAG); + response.setContent("{}"); + + RemoteConfigTemplate template = client.getTemplate(); + + assertEquals(TEST_ETAG, template.getETag()); + assertEquals(0, template.getParameters().size()); checkGetRequestHeader(interceptor.getLastRequest()); } diff --git a/src/test/resources/getRemoteConfig.json b/src/test/resources/getRemoteConfig.json new file mode 100644 index 000000000..86322f267 --- /dev/null +++ b/src/test/resources/getRemoteConfig.json @@ -0,0 +1,42 @@ +{ + "conditions": [ + { + "name": "ios_en", + "expression": "device.os == 'ios' && device.country in ['us', 'uk']", + "tagColor": "INDIGO" + }, + { + "name": "android_en", + "expression": "device.os == 'android' && device.country in ['us', 'uk']", + "tagColor": "GREEN" + } + ], + "parameters": { + "welcome_message_text": { + "defaultValue": { + "value": "welcome to app" + }, + "conditionalValues": { + "ios_en": { + "value": "welcome to app en" + } + }, + "description": "text for welcome message!" + }, + "header_text": { + "defaultValue": { + "useInAppDefault": true + } + } + }, + "parameterGroups": {}, + "version": { + "versionNumber": "17", + "updateOrigin": "ADMIN_SDK_NODE", + "updateType": "INCREMENTAL_UPDATE", + "updateUser": { + "email": "firebase-user@account.com" + }, + "updateTime": "Wed, 30 Sep 2020 17:56:07 GMT" + } +}