Skip to content

Commit 936043b

Browse files
Add the entry point for Remote Config (#478)
* Add the entry point for Remote Config * Remove the builder in RemoteConfig.java * Remove fromApp and use the constructor directly * Fix doc strings
1 parent 88482db commit 936043b

File tree

6 files changed

+341
-3
lines changed

6 files changed

+341
-3
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.remoteconfig;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.google.api.core.ApiFuture;
22+
import com.google.common.annotations.VisibleForTesting;
23+
import com.google.firebase.FirebaseApp;
24+
import com.google.firebase.ImplFirebaseTrampolines;
25+
import com.google.firebase.internal.CallableOperation;
26+
import com.google.firebase.internal.FirebaseService;
27+
28+
/**
29+
* This class is the entry point for all server-side Firebase Remote Config actions.
30+
*
31+
* <p>You can get an instance of {@link FirebaseRemoteConfig} via {@link #getInstance(FirebaseApp)},
32+
* and then use it to manage Remote Config templates.
33+
*/
34+
public final class FirebaseRemoteConfig {
35+
36+
private static final String SERVICE_ID = FirebaseRemoteConfig.class.getName();
37+
private final FirebaseApp app;
38+
private final FirebaseRemoteConfigClient remoteConfigClient;
39+
40+
@VisibleForTesting
41+
FirebaseRemoteConfig(FirebaseApp app, FirebaseRemoteConfigClient client) {
42+
this.app = checkNotNull(app);
43+
this.remoteConfigClient = checkNotNull(client);
44+
}
45+
46+
private FirebaseRemoteConfig(FirebaseApp app) {
47+
this(app, FirebaseRemoteConfigClientImpl.fromApp(app));
48+
}
49+
50+
/**
51+
* Gets the {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}.
52+
*
53+
* @return The {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}.
54+
*/
55+
public static FirebaseRemoteConfig getInstance() {
56+
return getInstance(FirebaseApp.getInstance());
57+
}
58+
59+
/**
60+
* Gets the {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}.
61+
*
62+
* @return The {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}.
63+
*/
64+
public static synchronized FirebaseRemoteConfig getInstance(FirebaseApp app) {
65+
FirebaseRemoteConfigService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID,
66+
FirebaseRemoteConfigService.class);
67+
if (service == null) {
68+
service = ImplFirebaseTrampolines.addService(app, new FirebaseRemoteConfigService(app));
69+
}
70+
return service.getInstance();
71+
}
72+
73+
/**
74+
* Gets the current active version of the Remote Config template.
75+
*
76+
* @return A {@link RemoteConfigTemplate}.
77+
* @throws FirebaseRemoteConfigException If an error occurs while getting the template.
78+
*/
79+
public RemoteConfigTemplate getTemplate() throws FirebaseRemoteConfigException {
80+
return getTemplateOp().call();
81+
}
82+
83+
/**
84+
* Similar to {@link #getTemplate()} but performs the operation asynchronously.
85+
*
86+
* @return An {@code ApiFuture} that completes with a {@link RemoteConfigTemplate} when
87+
* the template is available.
88+
*/
89+
public ApiFuture<RemoteConfigTemplate> getTemplateAsync() {
90+
return getTemplateOp().callAsync(app);
91+
}
92+
93+
private CallableOperation<RemoteConfigTemplate, FirebaseRemoteConfigException> getTemplateOp() {
94+
final FirebaseRemoteConfigClient remoteConfigClient = getRemoteConfigClient();
95+
return new CallableOperation<RemoteConfigTemplate, FirebaseRemoteConfigException>() {
96+
@Override
97+
protected RemoteConfigTemplate execute() throws FirebaseRemoteConfigException {
98+
return remoteConfigClient.getTemplate();
99+
}
100+
};
101+
}
102+
103+
@VisibleForTesting
104+
FirebaseRemoteConfigClient getRemoteConfigClient() {
105+
return remoteConfigClient;
106+
}
107+
108+
private static class FirebaseRemoteConfigService extends FirebaseService<FirebaseRemoteConfig> {
109+
110+
FirebaseRemoteConfigService(FirebaseApp app) {
111+
super(SERVICE_ID, new FirebaseRemoteConfig(app));
112+
}
113+
114+
@Override
115+
public void destroy() {
116+
// NOTE: We don't explicitly tear down anything here, but public methods of
117+
// FirebaseRemoteConfig will now fail because calls to getOptions() and getToken()
118+
// will hit FirebaseApp, which will throw once the app is deleted.
119+
}
120+
}
121+
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ public RemoteConfigTemplate getTemplate() throws FirebaseRemoteConfigException {
9595
.addAllHeaders(COMMON_HEADERS);
9696
IncomingHttpResponse response = httpClient.send(request);
9797
RemoteConfigTemplate parsed = httpClient.parse(response, RemoteConfigTemplate.class);
98-
parsed.setETag(getETag(response));
99-
return parsed;
98+
return parsed.setETag(getETag(response));
10099
}
101100

102101
private String getETag(IncomingHttpResponse response) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.firebase.remoteconfig;
1818

19+
import com.google.common.annotations.VisibleForTesting;
1920
import com.google.firebase.ErrorCode;
2021
import com.google.firebase.FirebaseException;
2122
import com.google.firebase.IncomingHttpResponse;
@@ -30,6 +31,11 @@ public final class FirebaseRemoteConfigException extends FirebaseException {
3031

3132
private final RemoteConfigErrorCode errorCode;
3233

34+
@VisibleForTesting
35+
FirebaseRemoteConfigException(@NonNull ErrorCode code, @NonNull String message) {
36+
this(code, message, null, null, null);
37+
}
38+
3339
public FirebaseRemoteConfigException(
3440
@NonNull ErrorCode errorCode,
3541
@NonNull String message,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public String getETag() {
2727
return this.etag;
2828
}
2929

30-
void setETag(String etag) {
30+
RemoteConfigTemplate setETag(String etag) {
3131
this.etag = etag;
32+
return this;
3233
}
3334
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.remoteconfig;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.assertSame;
22+
import static org.junit.Assert.assertTrue;
23+
import static org.junit.Assert.fail;
24+
25+
import com.google.firebase.ErrorCode;
26+
import com.google.firebase.FirebaseApp;
27+
import com.google.firebase.FirebaseOptions;
28+
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
29+
import com.google.firebase.auth.MockGoogleCredentials;
30+
import java.util.concurrent.ExecutionException;
31+
import org.junit.After;
32+
import org.junit.Test;
33+
34+
public class FirebaseRemoteConfigTest {
35+
36+
private static final FirebaseOptions TEST_OPTIONS = FirebaseOptions.builder()
37+
.setCredentials(new MockGoogleCredentials("test-token"))
38+
.setProjectId("test-project")
39+
.build();
40+
private static final FirebaseRemoteConfigException TEST_EXCEPTION =
41+
new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message");
42+
43+
@After
44+
public void tearDown() {
45+
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
46+
}
47+
48+
@Test
49+
public void testGetInstance() {
50+
FirebaseApp.initializeApp(TEST_OPTIONS);
51+
52+
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
53+
54+
assertSame(remoteConfig, FirebaseRemoteConfig.getInstance());
55+
}
56+
57+
@Test
58+
public void testGetInstanceByApp() {
59+
FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app");
60+
61+
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app);
62+
63+
assertSame(remoteConfig, FirebaseRemoteConfig.getInstance(app));
64+
}
65+
66+
@Test
67+
public void testDefaultRemoteConfigClient() {
68+
FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app");
69+
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app);
70+
71+
FirebaseRemoteConfigClient client = remoteConfig.getRemoteConfigClient();
72+
73+
assertTrue(client instanceof FirebaseRemoteConfigClientImpl);
74+
assertSame(client, remoteConfig.getRemoteConfigClient());
75+
String expectedUrl = "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig";
76+
assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getRemoteConfigUrl());
77+
}
78+
79+
@Test
80+
public void testAppDelete() {
81+
FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app");
82+
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app);
83+
assertNotNull(remoteConfig);
84+
85+
app.delete();
86+
87+
try {
88+
FirebaseRemoteConfig.getInstance(app);
89+
fail("No error thrown when getting remote config instance after deleting app");
90+
} catch (IllegalStateException expected) {
91+
// expected
92+
}
93+
}
94+
95+
@Test
96+
public void testRemoteConfigClientWithoutProjectId() {
97+
FirebaseOptions options = FirebaseOptions.builder()
98+
.setCredentials(new MockGoogleCredentials("test-token"))
99+
.build();
100+
FirebaseApp.initializeApp(options);
101+
102+
try {
103+
FirebaseRemoteConfig.getInstance();
104+
fail("No error thrown for missing project ID");
105+
} catch (IllegalArgumentException expected) {
106+
String message = "Project ID is required to access Remote Config service. Use a service "
107+
+ "account credential or set the project ID explicitly via FirebaseOptions. "
108+
+ "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT "
109+
+ "environment variable.";
110+
assertEquals(message, expected.getMessage());
111+
}
112+
}
113+
114+
private static final String TEST_ETAG = "etag-123456789012-1";
115+
116+
@Test
117+
public void testGetTemplate() throws FirebaseRemoteConfigException {
118+
MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate(
119+
new RemoteConfigTemplate().setETag(TEST_ETAG));
120+
FirebaseRemoteConfig remoteConfig = getRemoteConfig(client);
121+
122+
RemoteConfigTemplate template = remoteConfig.getTemplate();
123+
124+
assertEquals(TEST_ETAG, template.getETag());
125+
}
126+
127+
@Test
128+
public void testGetTemplateFailure() {
129+
MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION);
130+
FirebaseRemoteConfig remoteConfig = getRemoteConfig(client);
131+
132+
try {
133+
remoteConfig.getTemplate();
134+
} catch (FirebaseRemoteConfigException e) {
135+
assertSame(TEST_EXCEPTION, e);
136+
}
137+
}
138+
139+
@Test
140+
public void testGetTemplateAsync() throws Exception {
141+
MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate(
142+
new RemoteConfigTemplate().setETag(TEST_ETAG));
143+
FirebaseRemoteConfig remoteConfig = getRemoteConfig(client);
144+
145+
RemoteConfigTemplate template = remoteConfig.getTemplateAsync().get();
146+
147+
assertEquals(TEST_ETAG, template.getETag());
148+
}
149+
150+
@Test
151+
public void testGetTemplateAsyncFailure() throws InterruptedException {
152+
MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION);
153+
FirebaseRemoteConfig remoteConfig = getRemoteConfig(client);
154+
155+
try {
156+
remoteConfig.getTemplateAsync().get();
157+
} catch (ExecutionException e) {
158+
assertSame(TEST_EXCEPTION, e.getCause());
159+
}
160+
}
161+
162+
private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) {
163+
FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS);
164+
return new FirebaseRemoteConfig(app, client);
165+
}
166+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.remoteconfig;
18+
19+
public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{
20+
21+
private RemoteConfigTemplate resultTemplate;
22+
private FirebaseRemoteConfigException exception;
23+
24+
private MockRemoteConfigClient(RemoteConfigTemplate resultTemplate,
25+
FirebaseRemoteConfigException exception) {
26+
this.resultTemplate = resultTemplate;
27+
this.exception = exception;
28+
}
29+
30+
static MockRemoteConfigClient fromTemplate(RemoteConfigTemplate resultTemplate) {
31+
return new MockRemoteConfigClient(resultTemplate, null);
32+
}
33+
34+
static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) {
35+
return new MockRemoteConfigClient(null, exception);
36+
}
37+
38+
@Override
39+
public RemoteConfigTemplate getTemplate() throws FirebaseRemoteConfigException {
40+
if (exception != null) {
41+
throw exception;
42+
}
43+
return resultTemplate;
44+
}
45+
}

0 commit comments

Comments
 (0)