Skip to content

Commit bea7c89

Browse files
committed
Added Optimizely Config
1 parent 13c27ff commit bea7c89

File tree

7 files changed

+628
-3
lines changed

7 files changed

+628
-3
lines changed

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.optimizely.ab.bucketing.FeatureDecision;
2222
import com.optimizely.ab.bucketing.UserProfileService;
2323
import com.optimizely.ab.config.*;
24+
import com.optimizely.ab.config.optimizely.OptimizelyConfig;
2425
import com.optimizely.ab.config.parser.ConfigParseException;
2526
import com.optimizely.ab.error.ErrorHandler;
2627
import com.optimizely.ab.error.NoOpErrorHandler;
@@ -82,6 +83,8 @@ public class Optimizely implements AutoCloseable {
8283
final EventProcessor eventProcessor;
8384
@VisibleForTesting
8485
final ErrorHandler errorHandler;
86+
@VisibleForTesting
87+
final OptimizelyConfig optimizelyConfig;
8588

8689
private final ProjectConfigManager projectConfigManager;
8790

@@ -97,7 +100,8 @@ private Optimizely(@Nonnull EventHandler eventHandler,
97100
@Nonnull DecisionService decisionService,
98101
@Nullable UserProfileService userProfileService,
99102
@Nonnull ProjectConfigManager projectConfigManager,
100-
@Nonnull NotificationCenter notificationCenter
103+
@Nonnull NotificationCenter notificationCenter,
104+
@Nonnull OptimizelyConfig optimizelyConfig
101105
) {
102106
this.eventHandler = eventHandler;
103107
this.eventProcessor = eventProcessor;
@@ -106,6 +110,7 @@ private Optimizely(@Nonnull EventHandler eventHandler,
106110
this.userProfileService = userProfileService;
107111
this.projectConfigManager = projectConfigManager;
108112
this.notificationCenter = notificationCenter;
113+
this.optimizelyConfig = optimizelyConfig;
109114
}
110115

111116
/**
@@ -454,6 +459,25 @@ public Boolean getFeatureVariableBoolean(@Nonnull String featureKey,
454459
);
455460
}
456461

462+
/**
463+
* Get {@link OptimizelyConfig} containing experiments and features
464+
*
465+
* @return {@link OptimizelyConfig}
466+
*/
467+
public OptimizelyConfig getOptimizelyConfig() {
468+
try {
469+
ProjectConfig projectConfig = getProjectConfig();
470+
if (projectConfig == null) {
471+
logger.error("Optimizely instance is not valid, failing getOptimizelyConfig call.");
472+
return null;
473+
}
474+
return optimizelyConfig.getOptimizelyConfig(projectConfig);
475+
} catch (Exception exception) {
476+
logger.error("Unable to fetch config due to error:" + exception);
477+
}
478+
return null;
479+
}
480+
457481
/**
458482
* Get the Double value of the specified variable in the feature.
459483
*
@@ -1036,7 +1060,7 @@ public Builder withErrorHandler(ErrorHandler errorHandler) {
10361060
/**
10371061
* The withEventHandler has has been moved to the EventProcessor which takes a EventHandler in it's builder
10381062
* method.
1039-
* {@link com.optimizely.ab.event.BatchEventProcessor.Builder#withEventHandler(com.optimizely.ab.event.EventHandler)} label}
1063+
* {@link BatchEventProcessor.Builder#withEventHandler(EventHandler)} label}
10401064
* Please use that builder method instead.
10411065
*/
10421066
@Deprecated
@@ -1148,7 +1172,17 @@ public Optimizely build() {
11481172
eventProcessor = new ForwardingEventProcessor(eventHandler, notificationCenter);
11491173
}
11501174

1151-
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, notificationCenter);
1175+
// Default Optimizely Config Provider
1176+
OptimizelyConfig optimizelyConfig = new OptimizelyConfig();
1177+
1178+
return new Optimizely(eventHandler,
1179+
eventProcessor,
1180+
errorHandler,
1181+
decisionService,
1182+
userProfileService,
1183+
projectConfigManager,
1184+
notificationCenter,
1185+
optimizelyConfig);
11521186
}
11531187
}
11541188
}
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
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+
package com.optimizely.ab.config.optimizely;
17+
18+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import com.optimizely.ab.config.*;
21+
22+
import java.util.*;
23+
24+
/**
25+
* Interface for OptimizleyConfig
26+
*/
27+
@JsonIgnoreProperties(ignoreUnknown = true)
28+
public class OptimizelyConfig {
29+
30+
@JsonProperty("experimentsMap")
31+
private Map<String, OptimizelyExperiment> experimentsMap;
32+
33+
@JsonProperty("featuresMap")
34+
private Map<String, OptimizelyFeature> featuresMap;
35+
36+
@JsonProperty("revision")
37+
private String revision;
38+
39+
/**
40+
* create maps for experiment and features to be returned as one object
41+
*
42+
* @param projectConfig the current projectConfig
43+
* @return {@link OptimizelyConfig} containing experiments and features
44+
*/
45+
public OptimizelyConfig getOptimizelyConfig(ProjectConfig projectConfig) {
46+
revision = projectConfig.getRevision();
47+
updateExperimentsMap(projectConfig);
48+
updateFeaturesMap(projectConfig);
49+
return this;
50+
}
51+
52+
/**
53+
* Updates the experimentMap
54+
*
55+
* @param projectConfig the current projectConfig
56+
*/
57+
private void updateExperimentsMap(ProjectConfig projectConfig) {
58+
experimentsMap = new HashMap<>();
59+
// experiment id's of the experiments for all rollouts
60+
List<String> rolloutExperimentIds = getRolloutExperimentIds(projectConfig.getRollouts());
61+
// reduce feature variables to key value pair map
62+
Map<String, FeatureVariable> featureVariablesMap = getFeatureVariablesMap(projectConfig.getFeatureFlags());
63+
List<Experiment> experiments = projectConfig.getExperiments();
64+
if(experiments != null)
65+
for (Experiment experiment : experiments)
66+
if (!rolloutExperimentIds.contains(experiment.getId())) {
67+
Map<String, OptimizelyVariation> variationMap = new HashMap<>();
68+
// searching for variables in all variations
69+
List<Variation> variations = experiment.getVariations();
70+
if (variations != null)
71+
for (Variation variation : variations) {
72+
OptimizelyVariation optimizelyVariation = new OptimizelyVariation();
73+
optimizelyVariation.setId(variation.getId());
74+
optimizelyVariation.setKey(variation.getKey());
75+
List<String> features = projectConfig.getExperimentFeatureKeyMapping().get(experiment.getId());
76+
// set the value only if feature experiment
77+
optimizelyVariation.setFeatureEnabled(features != null && !features.isEmpty() ? variation.getFeatureEnabled() : null);
78+
optimizelyVariation.setVariablesMap(getMergedVariablesMap(projectConfig, variation, experiment.getId(), featureVariablesMap));
79+
variationMap.put(variation.getKey(), optimizelyVariation);
80+
}
81+
// creating experiment map having all the experiment details
82+
OptimizelyExperiment optimizelyExperiment = new OptimizelyExperiment();
83+
optimizelyExperiment.setId(experiment.getId());
84+
optimizelyExperiment.setKey(experiment.getKey());
85+
optimizelyExperiment.setVariationsMap(variationMap);
86+
experimentsMap.put(experiment.getKey(), optimizelyExperiment);
87+
}
88+
}
89+
90+
/**
91+
* Updates the featuresMap
92+
*
93+
* @param projectConfig the current projectConfig
94+
*/
95+
private void updateFeaturesMap(ProjectConfig projectConfig) {
96+
featuresMap = new HashMap<>();
97+
List<FeatureFlag> featureFlags = projectConfig.getFeatureFlags();
98+
if(featureFlags != null)
99+
// get features map
100+
for (FeatureFlag featureFlag : featureFlags) {
101+
// setting up feature
102+
OptimizelyFeature optimizelyFeature = new OptimizelyFeature();
103+
optimizelyFeature.setId(featureFlag.getId());
104+
optimizelyFeature.setKey(featureFlag.getKey());
105+
optimizelyFeature.setExperimentsMap(getFeatureExperimentMap(featureFlag.getExperimentIds()));
106+
optimizelyFeature.setVariablesMap(getFeatureVariableMap(featureFlag.getVariables()));
107+
featuresMap.put(featureFlag.getKey(), optimizelyFeature);
108+
}
109+
}
110+
111+
/**
112+
* Gets the feature experiment only
113+
*
114+
* @param experimentIds feature experiments to be filtered for feature map
115+
* @return experimentMap for feature map
116+
*/
117+
private Map<String, OptimizelyExperiment> getFeatureExperimentMap(List<String> experimentIds) {
118+
Map<String, OptimizelyExperiment> optimizelyExperimentMap = new HashMap<>();
119+
if (experimentIds != null) {
120+
for (String experimentId : experimentIds) {
121+
experimentsMap.forEach((experimentKey, experiment) -> {
122+
if(experiment.getId().equals(experimentId))
123+
optimizelyExperimentMap.put(experimentKey, experiment);
124+
});
125+
}
126+
}
127+
return optimizelyExperimentMap;
128+
}
129+
130+
/**
131+
* Add the details for feature variables
132+
*
133+
* @param featureVariables feature variables for feature experiments
134+
* @return variables map for feature map
135+
*/
136+
private Map<String, OptimizelyVariable> getFeatureVariableMap(List<FeatureVariable> featureVariables) {
137+
Map<String, OptimizelyVariable> optimizelyVariableMap = new HashMap<>();
138+
if (featureVariables != null) {
139+
for (FeatureVariable featureVariable : featureVariables) {
140+
OptimizelyVariable optimizelyVariable = new OptimizelyVariable();
141+
optimizelyVariable.setId(featureVariable.getId());
142+
optimizelyVariable.setKey(featureVariable.getKey());
143+
optimizelyVariable.setType(featureVariable.getType().getVariableType().toLowerCase());
144+
optimizelyVariable.setValue(featureVariable.getDefaultValue());
145+
optimizelyVariableMap.put(featureVariable.getKey(), optimizelyVariable);
146+
}
147+
}
148+
return optimizelyVariableMap;
149+
}
150+
151+
/**
152+
* Returns list of experiment id's of the experiments for all rollouts
153+
*
154+
* @param rollouts rollouts from {@link ProjectConfig}
155+
* @return List of {@link Experiment} id's
156+
*/
157+
private List<String> getRolloutExperimentIds(List<Rollout> rollouts) {
158+
List<String> experimentIds = new ArrayList<>();
159+
// project having rollouts
160+
if(rollouts != null)
161+
for (Rollout rollout : rollouts)
162+
if (rollout.getExperiments() != null)
163+
for (Experiment experiment : rollout.getExperiments())
164+
experimentIds.add(experiment.getId());
165+
return experimentIds;
166+
}
167+
168+
/**
169+
* Returns Map for all features which contains List of Variables
170+
*
171+
* @param featureFlags features rollouts from {@link ProjectConfig}
172+
* @return {@link FeatureVariable} for all the features in the current project
173+
*/
174+
private Map<String, FeatureVariable> getFeatureVariablesMap(List<FeatureFlag> featureFlags) {
175+
Map<String, FeatureVariable> featureVariablesMap = new HashMap<>();
176+
if (featureFlags != null)
177+
for(FeatureFlag featureFlag : featureFlags)
178+
for(FeatureVariable variable : featureFlag.getVariables())
179+
featureVariablesMap.put(variable.getId(), variable);
180+
return featureVariablesMap;
181+
}
182+
183+
/**
184+
* Merges feature key and type from feature variables to variation variables
185+
*
186+
* @param projectConfig the current projectConfig
187+
* @param variation variation for which the value of {@link FeatureVariable} needs to be merged
188+
* @param experimentId {@link Experiment} id for getting its feature id
189+
* @param featureVariablesMap map for {@link FeatureVariable} of a {@link FeatureFlag}
190+
* @return optimizelyVariableMap returns the map after merging the value of {@link FeatureVariable}
191+
*/
192+
private Map<String, OptimizelyVariable> getMergedVariablesMap(ProjectConfig projectConfig,
193+
Variation variation,
194+
String experimentId,
195+
Map<String, FeatureVariable> featureVariablesMap) {
196+
197+
Map<String, OptimizelyVariable> optimizelyVariableMap = new HashMap<>();
198+
Map<String, List<FeatureVariable>> featureIDVariablesMap = new HashMap<>();
199+
200+
List<String> featureList = generateExperimentFeatureIdMapping(projectConfig.getFeatureFlags()).get(experimentId);
201+
List<FeatureVariableUsageInstance> featureVariableUsageInstances = variation.getFeatureVariableUsageInstances();
202+
203+
List<FeatureFlag> featureFlags = projectConfig.getFeatureFlags();
204+
if (featureFlags != null) {
205+
for (FeatureFlag featureFlag : featureFlags)
206+
featureIDVariablesMap.put(featureFlag.getId(), featureFlag.getVariables());
207+
}
208+
209+
if (featureList != null) {
210+
if (featureVariableUsageInstances != null) {
211+
for (FeatureVariableUsageInstance featureVariableUsageInstance : featureVariableUsageInstances) {
212+
OptimizelyVariable optimizelyVariable = new OptimizelyVariable();
213+
optimizelyVariable.setId(featureVariableUsageInstance.getId());
214+
optimizelyVariable.setValue(variation.getFeatureEnabled()
215+
? featureVariableUsageInstance.getValue()
216+
: featureVariablesMap.get(featureVariableUsageInstance.getId()).getDefaultValue());
217+
optimizelyVariable.setKey(featureVariablesMap.get(featureVariableUsageInstance.getId()).getKey());
218+
optimizelyVariable.setType(featureVariablesMap.get(featureVariableUsageInstance.getId()).getType().getVariableType().toLowerCase());
219+
optimizelyVariableMap.put(featureVariablesMap.get(featureVariableUsageInstance.getId()).getKey(), optimizelyVariable);
220+
}
221+
}
222+
featureList.forEach(featId -> {
223+
if(featureIDVariablesMap.get(featId) != null) {
224+
featureIDVariablesMap.get(featId).forEach(featureVariable -> {
225+
if (!optimizelyVariableMap.containsKey(featureVariable.getKey())) {
226+
OptimizelyVariable optimizelyVariable = new OptimizelyVariable();
227+
optimizelyVariable.setId(featureVariable.getId());
228+
optimizelyVariable.setValue(featureVariable.getDefaultValue());
229+
optimizelyVariable.setKey(featureVariable.getKey());
230+
optimizelyVariable.setType(featureVariable.getType().getVariableType().toLowerCase());
231+
optimizelyVariableMap.put(featureVariable.getKey(), optimizelyVariable);
232+
}
233+
});
234+
}
235+
});
236+
}
237+
return optimizelyVariableMap;
238+
}
239+
240+
public static Map<String, List<String>> generateExperimentFeatureIdMapping(List<FeatureFlag> featureFlags) {
241+
Map<String, List<String>> experimentFeatureMap = new HashMap<>();
242+
for (FeatureFlag featureFlag : featureFlags) {
243+
for (String experimentId : featureFlag.getExperimentIds()) {
244+
if (experimentFeatureMap.containsKey(experimentId)) {
245+
experimentFeatureMap.get(experimentId).add(featureFlag.getId());
246+
} else {
247+
ArrayList<String> featureFlagKeysList = new ArrayList<>();
248+
featureFlagKeysList.add(featureFlag.getId());
249+
experimentFeatureMap.put(experimentId, featureFlagKeysList);
250+
}
251+
}
252+
}
253+
return Collections.unmodifiableMap(experimentFeatureMap);
254+
}
255+
}

0 commit comments

Comments
 (0)