Skip to content

Commit 464370f

Browse files
aliabbasrizviMike Davis
authored andcommitted
Fixing how project config is updated. Also, fixing issue in Optimizely.close() (#309)
1 parent 04fd855 commit 464370f

File tree

2 files changed

+89
-53
lines changed

2 files changed

+89
-53
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ public boolean isValid() {
120120
}
121121

122122
/**
123-
* Helper method which checks if Object is an instance of Closeable and calls close() on it.
123+
* Helper method which checks if Object is an instance of AutoCloseable and calls close() on it.
124124
*/
125125
private void tryClose(Object obj) {
126-
if (!(obj instanceof Closeable)) {
126+
if (!(obj instanceof AutoCloseable)) {
127127
return;
128128
}
129129

130130
try {
131-
((Closeable) obj).close();
131+
((AutoCloseable) obj).close();
132132
} catch (Exception e) {
133133
logger.warn("Unexpected exception on trying to close {}.", obj);
134134
}
@@ -214,7 +214,7 @@ private Variation activate(@Nullable ProjectConfig projectConfig,
214214
}
215215
Map<String, ?> copiedAttributes = copyAttributes(attributes);
216216
// bucket the user to the given experiment and dispatch an impression event
217-
Variation variation = getVariation(experiment, userId, copiedAttributes, projectConfig);
217+
Variation variation = getVariation(projectConfig, experiment, userId, copiedAttributes);
218218
if (variation == null) {
219219
logger.info("Not activating user \"{}\" for experiment \"{}\".", userId, experiment.getKey());
220220
return null;
@@ -370,6 +370,20 @@ public Boolean isFeatureEnabled(@Nonnull String featureKey,
370370
public Boolean isFeatureEnabled(@Nonnull String featureKey,
371371
@Nonnull String userId,
372372
@Nonnull Map<String, ?> attributes) {
373+
ProjectConfig projectConfig = getProjectConfig();
374+
if (projectConfig == null) {
375+
logger.error("Optimizely instance is not valid, failing isFeatureEnabled call.");
376+
return false;
377+
}
378+
379+
return isFeatureEnabled(projectConfig, featureKey, userId, attributes);
380+
}
381+
382+
@Nonnull
383+
private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,
384+
@Nonnull String featureKey,
385+
@Nonnull String userId,
386+
@Nonnull Map<String, ?> attributes) {
373387
if (featureKey == null) {
374388
logger.warn("The featureKey parameter must be nonnull.");
375389
return false;
@@ -378,12 +392,6 @@ public Boolean isFeatureEnabled(@Nonnull String featureKey,
378392
return false;
379393
}
380394

381-
ProjectConfig projectConfig = getProjectConfig();
382-
if (projectConfig == null) {
383-
logger.error("Optimizely instance is not valid, failing isFeatureEnabled call.");
384-
return false;
385-
}
386-
387395
FeatureFlag featureFlag = projectConfig.getFeatureKeyMapping().get(featureKey);
388396
if (featureFlag == null) {
389397
logger.info("No feature flag was found for key \"{}\".", featureKey);
@@ -753,7 +761,7 @@ public List<String> getEnabledFeatures(@Nonnull String userId, @Nonnull Map<Stri
753761
Map<String, ?> copiedAttributes = copyAttributes(attributes);
754762
for (FeatureFlag featureFlag : projectConfig.getFeatureFlags()) {
755763
String featureKey = featureFlag.getKey();
756-
if (isFeatureEnabled(featureKey, userId, copiedAttributes))
764+
if (isFeatureEnabled(projectConfig, featureKey, userId, copiedAttributes))
757765
enabledFeaturesList.add(featureKey);
758766
}
759767

@@ -773,20 +781,20 @@ public Variation getVariation(@Nonnull Experiment experiment,
773781
public Variation getVariation(@Nonnull Experiment experiment,
774782
@Nonnull String userId,
775783
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
776-
return getVariation(experiment, userId, attributes, getProjectConfig());
784+
return getVariation(getProjectConfig(), experiment, userId, attributes);
777785
}
778786

779787
@Nullable
780-
public Variation getVariation(@Nonnull Experiment experiment,
781-
@Nonnull String userId,
782-
@Nonnull Map<String, ?> attributes,
783-
@Nonnull ProjectConfig projectConfig) throws UnknownExperimentException {
788+
private Variation getVariation(@Nonnull ProjectConfig projectConfig,
789+
@Nonnull Experiment experiment,
790+
@Nonnull String userId,
791+
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
784792
Map<String, ?> copiedAttributes = copyAttributes(attributes);
785793
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes, projectConfig);
786794

787795
String notificationType = NotificationCenter.DecisionNotificationType.AB_TEST.toString();
788796

789-
if (getProjectConfig().getExperimentFeatureKeyMapping().get(experiment.getId()) != null) {
797+
if (projectConfig.getExperimentFeatureKeyMapping().get(experiment.getId()) != null) {
790798
notificationType = NotificationCenter.DecisionNotificationType.FEATURE_TEST.toString();
791799
}
792800

@@ -835,7 +843,7 @@ public Variation getVariation(@Nonnull String experimentKey,
835843
return null;
836844
}
837845

838-
return getVariation(experiment, userId, attributes, projectConfig);
846+
return getVariation(projectConfig, experiment, userId, attributes);
839847
}
840848

841849
/**

core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,28 @@ public OptimizelyTest(String validDatafile, String noAudienceDatafile) throws Co
145145
}
146146

147147
@Test
148-
public void testClose() throws IOException {
148+
public void testClose() throws Exception {
149+
// Check for AutoCloseable
150+
EventHandler mockAutoCloseableEventHandler = mock(
151+
EventHandler.class,
152+
withSettings().extraInterfaces(AutoCloseable.class)
153+
);
154+
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
155+
ProjectConfigManager.class,
156+
withSettings().extraInterfaces(AutoCloseable.class)
157+
);
158+
159+
Optimizely optimizely = Optimizely.builder()
160+
.withEventHandler(mockAutoCloseableEventHandler)
161+
.withConfigManager(mockAutoCloseableProjectConfigManager)
162+
.build();
163+
164+
optimizely.close();
165+
166+
verify((AutoCloseable) mockAutoCloseableEventHandler).close();
167+
verify((AutoCloseable) mockAutoCloseableProjectConfigManager).close();
168+
169+
// Check for Closeable
149170
EventHandler mockCloseableEventHandler = mock(
150171
EventHandler.class,
151172
withSettings().extraInterfaces(Closeable.class)
@@ -155,7 +176,7 @@ public void testClose() throws IOException {
155176
withSettings().extraInterfaces(Closeable.class)
156177
);
157178

158-
Optimizely optimizely = Optimizely.builder()
179+
optimizely = Optimizely.builder()
159180
.withEventHandler(mockCloseableEventHandler)
160181
.withConfigManager(mockCloseableProjectConfigManager)
161182
.build();
@@ -167,44 +188,44 @@ public void testClose() throws IOException {
167188
}
168189

169190
@Test
170-
public void testCloseConfigManagerThrowsException() throws IOException {
171-
EventHandler mockCloseableEventHandler = mock(
191+
public void testCloseConfigManagerThrowsException() throws Exception {
192+
EventHandler mockAutoCloseableEventHandler = mock(
172193
EventHandler.class,
173-
withSettings().extraInterfaces(Closeable.class)
194+
withSettings().extraInterfaces(AutoCloseable.class)
174195
);
175-
ProjectConfigManager mockCloseableProjectConfigManager = mock(
196+
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
176197
ProjectConfigManager.class,
177-
withSettings().extraInterfaces(Closeable.class)
198+
withSettings().extraInterfaces(AutoCloseable.class)
178199
);
179200

180201
Optimizely optimizely = Optimizely.builder()
181-
.withEventHandler(mockCloseableEventHandler)
182-
.withConfigManager(mockCloseableProjectConfigManager)
202+
.withEventHandler(mockAutoCloseableEventHandler)
203+
.withConfigManager(mockAutoCloseableProjectConfigManager)
183204
.build();
184205

185-
doThrow(new IOException()).when((Closeable) mockCloseableProjectConfigManager).close();
186-
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockCloseableProjectConfigManager + ".");
206+
doThrow(new IOException()).when((AutoCloseable) mockAutoCloseableProjectConfigManager).close();
207+
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockAutoCloseableProjectConfigManager + ".");
187208
optimizely.close();
188209
}
189210

190211
@Test
191-
public void testCloseEventHandlerThrowsException() throws IOException {
192-
EventHandler mockCloseableEventHandler = mock(
212+
public void testCloseEventHandlerThrowsException() throws Exception {
213+
EventHandler mockAutoCloseableEventHandler = mock(
193214
EventHandler.class,
194-
withSettings().extraInterfaces(Closeable.class)
215+
withSettings().extraInterfaces(AutoCloseable.class)
195216
);
196-
ProjectConfigManager mockCloseableProjectConfigManager = mock(
217+
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
197218
ProjectConfigManager.class,
198-
withSettings().extraInterfaces(Closeable.class)
219+
withSettings().extraInterfaces(AutoCloseable.class)
199220
);
200221

201222
Optimizely optimizely = Optimizely.builder()
202-
.withEventHandler(mockCloseableEventHandler)
203-
.withConfigManager(mockCloseableProjectConfigManager)
223+
.withEventHandler(mockAutoCloseableEventHandler)
224+
.withConfigManager(mockAutoCloseableProjectConfigManager)
204225
.build();
205226

206-
doThrow(new IOException()).when((Closeable) mockCloseableEventHandler).close();
207-
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockCloseableEventHandler + ".");
227+
doThrow(new IOException()).when((AutoCloseable) mockAutoCloseableEventHandler).close();
228+
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockAutoCloseableEventHandler + ".");
208229
optimizely.close();
209230
}
210231

@@ -2861,9 +2882,9 @@ public void getEnabledFeaturesWithListenerMultipleFeatureEnabled() throws Except
28612882

28622883
/**
28632884
* Verify {@link Optimizely#getEnabledFeatures(String, Map)} calls into
2864-
* {@link Optimizely#isFeatureEnabled(String, String, Map)} for each featureFlag sending
2885+
* {@link DecisionService#getVariationForFeature} for each featureFlag sending
28652886
* userId and emptyMap and Mocked {@link Optimizely#isFeatureEnabled(String, String, Map)}
2866-
* to return false so {@link Optimizely#getEnabledFeatures(String, Map)} will
2887+
* to return feature disabled so {@link Optimizely#getEnabledFeatures(String, Map)} will
28672888
* return empty List of FeatureFlags and no notification listener will get called.
28682889
*/
28692890
@Test
@@ -2873,13 +2894,16 @@ public void getEnabledFeaturesWithNoFeatureEnabled() throws Exception {
28732894
isListenerCalled = false;
28742895
Optimizely spyOptimizely = spy(Optimizely.builder(validDatafile, mockEventHandler)
28752896
.withConfig(validProjectConfig)
2897+
.withDecisionService(mockDecisionService)
28762898
.build());
2877-
doReturn(false).when(spyOptimizely).isFeatureEnabled(
2878-
any(String.class),
2879-
eq(genericUserId),
2880-
eq(Collections.<String, String>emptyMap())
2881-
);
28822899

2900+
FeatureDecision featureDecision = new FeatureDecision(null, null, FeatureDecision.DecisionSource.ROLLOUT);
2901+
doReturn(featureDecision).when(mockDecisionService).getVariationForFeature(
2902+
any(FeatureFlag.class),
2903+
anyString(),
2904+
anyMapOf(String.class, String.class),
2905+
any(ProjectConfig.class)
2906+
);
28832907
int notificationId = spyOptimizely.addDecisionNotificationHandler( decisionNotification -> { });
28842908

28852909
ArrayList<String> featureFlags = (ArrayList<String>) spyOptimizely.getEnabledFeatures(genericUserId,
@@ -4839,23 +4863,27 @@ public void getEnabledFeatureWithNullUserID() throws ConfigParseException {
48394863

48404864
/**
48414865
* Verify {@link Optimizely#getEnabledFeatures(String, Map)} calls into
4842-
* {@link Optimizely#isFeatureEnabled(String, String, Map)} for each featureFlag sending
4843-
* userId and emptyMap and Mocked {@link Optimizely#isFeatureEnabled(String, String, Map)}
4844-
* to return false so {@link Optimizely#getEnabledFeatures(String, Map)} will
4866+
* {@link DecisionService#getVariationForFeature} to return feature
4867+
* disabled so {@link Optimizely#getEnabledFeatures(String, Map)} will
48454868
* return empty List of FeatureFlags.
48464869
*/
48474870
@Test
4848-
public void getEnabledFeatureWithMockIsFeatureEnabledToReturnFalse() throws ConfigParseException {
4871+
public void getEnabledFeatureWithMockDecisionService() throws ConfigParseException {
48494872
assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString()));
48504873

48514874
Optimizely spyOptimizely = spy(Optimizely.builder(validDatafile, mockEventHandler)
48524875
.withConfig(validProjectConfig)
4876+
.withDecisionService(mockDecisionService)
48534877
.build());
4854-
doReturn(false).when(spyOptimizely).isFeatureEnabled(
4855-
any(String.class),
4856-
eq(genericUserId),
4857-
eq(Collections.<String, String>emptyMap())
4878+
4879+
FeatureDecision featureDecision = new FeatureDecision(null, null, FeatureDecision.DecisionSource.ROLLOUT);
4880+
doReturn(featureDecision).when(mockDecisionService).getVariationForFeature(
4881+
any(FeatureFlag.class),
4882+
anyString(),
4883+
anyMapOf(String.class, String.class),
4884+
any(ProjectConfig.class)
48584885
);
4886+
48594887
ArrayList<String> featureFlags = (ArrayList<String>) spyOptimizely.getEnabledFeatures(genericUserId,
48604888
Collections.<String, String>emptyMap());
48614889
assertTrue(featureFlags.isEmpty());

0 commit comments

Comments
 (0)