Skip to content

Commit 1cf2d72

Browse files
authored
feat: Added ODPSegmentManager (#484)
## Summary Added an ODPSegmentManager which provides functionality to fetch segments from ODP server and cache and re-use them. ## Test plan - Manually tested thoroughly - Added new unit tests - Existing unit tests pass - All Full stack compatibility suite tests pass ## JIRA [OASIS-8383](https://optimizely.atlassian.net/browse/OASIS-8383)
1 parent 5866f7b commit 1cf2d72

File tree

5 files changed

+448
-0
lines changed

5 files changed

+448
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
*
3+
* Copyright 2022, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.odp;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
22+
public class ODPConfig {
23+
24+
private String apiKey;
25+
26+
private String apiHost;
27+
28+
private List<String> allSegments;
29+
30+
public ODPConfig(String apiKey, String apiHost, List<String> allSegments) {
31+
this.apiKey = apiKey;
32+
this.apiHost = apiHost;
33+
this.allSegments = allSegments;
34+
}
35+
36+
public ODPConfig(String apiKey, String apiHost) {
37+
this(apiKey, apiHost, Collections.emptyList());
38+
}
39+
40+
public synchronized Boolean isReady() {
41+
return !(
42+
this.apiKey == null || this.apiKey.isEmpty()
43+
|| this.apiHost == null || this.apiHost.isEmpty()
44+
);
45+
}
46+
47+
public synchronized Boolean hasSegments() {
48+
return allSegments != null && !allSegments.isEmpty();
49+
}
50+
51+
public synchronized void setApiKey(String apiKey) {
52+
this.apiKey = apiKey;
53+
}
54+
55+
public synchronized void setApiHost(String apiHost) {
56+
this.apiHost = apiHost;
57+
}
58+
59+
public synchronized String getApiKey() {
60+
return apiKey;
61+
}
62+
63+
public synchronized String getApiHost() {
64+
return apiHost;
65+
}
66+
67+
public synchronized List<String> getAllSegments() {
68+
return allSegments;
69+
}
70+
71+
public synchronized void setAllSegments(List<String> allSegments) {
72+
this.allSegments = allSegments;
73+
}
74+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
*
3+
* Copyright 2022, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.odp;
18+
19+
import com.optimizely.ab.internal.Cache;
20+
import com.optimizely.ab.internal.DefaultLRUCache;
21+
import com.optimizely.ab.odp.parser.ResponseJsonParser;
22+
import com.optimizely.ab.odp.parser.ResponseJsonParserFactory;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import java.util.Collections;
27+
import java.util.List;
28+
29+
public class ODPSegmentManager {
30+
31+
private static final Logger logger = LoggerFactory.getLogger(ODPSegmentManager.class);
32+
33+
private static final String SEGMENT_URL_PATH = "/v3/graphql";
34+
35+
private final ODPApiManager apiManager;
36+
37+
private final ODPConfig odpConfig;
38+
39+
private final Cache<List<String>> segmentsCache;
40+
41+
public ODPSegmentManager(ODPConfig odpConfig, ODPApiManager apiManager) {
42+
this(odpConfig, apiManager, Cache.DEFAULT_MAX_SIZE, Cache.DEFAULT_TIMEOUT_SECONDS);
43+
}
44+
45+
public ODPSegmentManager(ODPConfig odpConfig, ODPApiManager apiManager, Cache<List<String>> cache) {
46+
this.apiManager = apiManager;
47+
this.odpConfig = odpConfig;
48+
this.segmentsCache = cache;
49+
}
50+
51+
public ODPSegmentManager(ODPConfig odpConfig, ODPApiManager apiManager, Integer cacheSize, Integer cacheTimeoutSeconds) {
52+
this.apiManager = apiManager;
53+
this.odpConfig = odpConfig;
54+
this.segmentsCache = new DefaultLRUCache<>(cacheSize, cacheTimeoutSeconds);
55+
}
56+
57+
public List<String> getQualifiedSegments(String fsUserId) {
58+
return getQualifiedSegments(ODPUserKey.FS_USER_ID, fsUserId, Collections.emptyList());
59+
}
60+
public List<String> getQualifiedSegments(String fsUserId, List<ODPSegmentOption> options) {
61+
return getQualifiedSegments(ODPUserKey.FS_USER_ID, fsUserId, options);
62+
}
63+
64+
public List<String> getQualifiedSegments(ODPUserKey userKey, String userValue) {
65+
return getQualifiedSegments(userKey, userValue, Collections.emptyList());
66+
}
67+
68+
public List<String> getQualifiedSegments(ODPUserKey userKey, String userValue, List<ODPSegmentOption> options) {
69+
if (!odpConfig.isReady()) {
70+
logger.error("Audience segments fetch failed (ODP is not enabled)");
71+
return Collections.emptyList();
72+
}
73+
74+
if (!odpConfig.hasSegments()) {
75+
logger.debug("No Segments are used in the project, Not Fetching segments. Returning empty list");
76+
return Collections.emptyList();
77+
}
78+
79+
List<String> qualifiedSegments;
80+
String cacheKey = getCacheKey(userKey.getKeyString(), userValue);
81+
82+
if (options.contains(ODPSegmentOption.RESET_CACHE)) {
83+
segmentsCache.reset();
84+
} else if (!options.contains(ODPSegmentOption.IGNORE_CACHE)) {
85+
qualifiedSegments = segmentsCache.lookup(cacheKey);
86+
if (qualifiedSegments != null) {
87+
logger.debug("ODP Cache Hit. Returning segments from Cache.");
88+
return qualifiedSegments;
89+
}
90+
}
91+
92+
logger.debug("ODP Cache Miss. Making a call to ODP Server.");
93+
94+
ResponseJsonParser parser = ResponseJsonParserFactory.getParser();
95+
String qualifiedSegmentsResponse = apiManager.fetchQualifiedSegments(odpConfig.getApiKey(), odpConfig.getApiHost() + SEGMENT_URL_PATH, userKey.getKeyString(), userValue, odpConfig.getAllSegments());
96+
qualifiedSegments = parser.parseQualifiedSegments(qualifiedSegmentsResponse);
97+
98+
if (qualifiedSegments != null && !options.contains(ODPSegmentOption.IGNORE_CACHE)) {
99+
segmentsCache.save(cacheKey, qualifiedSegments);
100+
}
101+
102+
return qualifiedSegments;
103+
}
104+
105+
private String getCacheKey(String userKey, String userValue) {
106+
return userKey + "-$-" + userValue;
107+
}
108+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
*
3+
* Copyright 2022, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.odp;
18+
19+
public enum ODPSegmentOption {
20+
21+
IGNORE_CACHE,
22+
23+
RESET_CACHE;
24+
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
*
3+
* Copyright 2022, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.odp;
18+
19+
public enum ODPUserKey {
20+
21+
VUID("vuid"),
22+
23+
FS_USER_ID("fs_user_id");
24+
25+
private final String keyString;
26+
27+
ODPUserKey(String keyString) {
28+
this.keyString = keyString;
29+
}
30+
31+
public String getKeyString() {
32+
return keyString;
33+
}
34+
}

0 commit comments

Comments
 (0)