io.harness.cf.client.api.StorageRepository Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ff-java-server-sdk Show documentation
Show all versions of ff-java-server-sdk Show documentation
Harness Feature Flag Java Server SDK
The newest version!
package io.harness.cf.client.api;
import com.google.gson.Gson;
import io.harness.cf.client.common.Cache;
import io.harness.cf.client.common.Storage;
import io.harness.cf.client.common.Utils;
import io.harness.cf.model.*;
import java.util.*;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class StorageRepository implements Repository {
private final Cache cache;
private Storage store;
private final RepositoryCallback callback;
private final boolean cachePreviousFeatureConfigVersion;
public StorageRepository(
@NonNull Cache cache,
RepositoryCallback callback,
boolean cachePreviousFeatureConfigVersion) {
this.cache = cache;
this.callback = callback;
this.cachePreviousFeatureConfigVersion = cachePreviousFeatureConfigVersion;
}
public StorageRepository(
@NonNull Cache cache,
Storage store,
RepositoryCallback callback,
boolean cachePreviousFeatureConfigVersion) {
this(cache, callback, cachePreviousFeatureConfigVersion);
this.store = store;
}
public Optional getFlag(@NonNull String identifier, boolean cacheable) {
final String flagKey = formatFlagKey(identifier);
FeatureConfig flag = (FeatureConfig) cache.get(flagKey);
if (flag != null) {
return Optional.of(flag);
}
if (this.store != null) {
flag = (FeatureConfig) store.get(flagKey);
if (flag != null && cacheable) {
cache.set(flagKey, flag);
}
return Optional.ofNullable(flag);
}
return Optional.empty();
}
@Override
public Optional getFlag(@NonNull String identifier) {
return getFlag(identifier, true);
}
public List getAllFeatureIdentifiers(String prefix) {
List identifiers = new LinkedList<>();
List keys = cache.keys();
String flagPrefix = "flags/";
for (String key : keys) {
if (key.startsWith(flagPrefix)) {
// Strip the flag prefix
String strippedKey = key.substring(flagPrefix.length());
// If prefix is empty, add all stripped keys, otherwise check for prefix match
if (prefix.isEmpty() || strippedKey.startsWith(prefix)) {
identifiers.add(strippedKey);
}
}
}
return identifiers;
}
public Optional getCurrentAndPreviousFeatureConfig(@NonNull String identifier) {
final String flagKey = formatFlagKey(identifier);
final String pFlagKey = formatPrevFlagKey(identifier);
FeatureConfig pFlag = (FeatureConfig) cache.get(pFlagKey);
FeatureConfig cFlag = (FeatureConfig) cache.get(flagKey);
if (cFlag != null) {
return Optional.of(new FeatureConfig[] {pFlag, cFlag});
}
// if we don't have it in cache we check the file
if (this.store != null) {
pFlag = (FeatureConfig) store.get(pFlagKey);
cFlag = (FeatureConfig) store.get(flagKey);
if (pFlag != null) {
cache.set(pFlagKey, pFlag);
}
if (cFlag != null) {
cache.set(flagKey, cFlag);
}
return Optional.of(new FeatureConfig[] {pFlag, cFlag});
}
return Optional.empty();
}
public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) {
Gson gson = new Gson();
final String flagKey = formatFlagKey(identifier);
final String pFlagKey = formatPrevFlagKey(identifier);
FeatureConfig pFlag = (FeatureConfig) cache.get(pFlagKey);
FeatureConfig cFlag = (FeatureConfig) cache.get(flagKey);
if (cFlag != null) {
FeatureSnapshot deepCopySnapshot =
gson.fromJson(gson.toJson(new FeatureSnapshot(cFlag, pFlag)), FeatureSnapshot.class);
return deepCopySnapshot;
}
// if we don't have it in cache we check the file
if (this.store != null) {
pFlag = (FeatureConfig) store.get(pFlagKey);
cFlag = (FeatureConfig) store.get(flagKey);
if (pFlag != null) {
cache.set(pFlagKey, pFlag);
}
if (cFlag != null) {
cache.set(flagKey, cFlag);
}
FeatureSnapshot deepCopySnapshot =
gson.fromJson(gson.toJson(new FeatureSnapshot(cFlag, pFlag)), FeatureSnapshot.class);
return deepCopySnapshot;
}
return null;
}
public Optional getSegment(@NonNull String identifier, boolean cacheable) {
final String segmentKey = formatSegmentKey(identifier);
Segment segment = (Segment) cache.get(segmentKey);
if (segment != null) {
return Optional.of(segment);
}
if (this.store != null) {
segment = (Segment) store.get(segmentKey);
if (segment != null && cacheable) {
cache.set(segmentKey, segment);
}
return Optional.ofNullable(segment);
}
return Optional.empty();
}
@Override
public Optional getSegment(@NonNull String identifier) {
return getSegment(identifier, true);
}
@Override
public List findFlagsBySegment(@NonNull String segment) {
List result = new ArrayList<>();
List keys = this.cache.keys();
if (store != null) {
log.debug("Store is available, load all keys");
keys = store.keys();
}
for (String key : keys) {
final Optional optionalFeatureConfig = getFlag(key);
if (!optionalFeatureConfig.isPresent()) {
log.debug("Flag not found {}, continue...", key);
continue;
}
final FeatureConfig flag = optionalFeatureConfig.get();
if (Utils.isEmpty(flag.getRules())) {
log.debug("Flag {} doesn't contain any rule, continue...", key);
continue;
}
for (ServingRule rule : flag.getRules()) {
for (Clause clause : rule.getClauses()) {
if (clause.getOp().equals(Operators.SEGMENT_MATCH)
&& clause.getValues().contains(segment)) {
log.debug("Flag {} evaluated in segments", flag.getFeature());
result.add(flag.getFeature());
}
}
}
}
return result;
}
@Override
public void setFlag(@NonNull String identifier, @NonNull FeatureConfig featureConfig) {
if (isFlagOutdated(identifier, featureConfig)) {
log.debug("Flag {} already exists", identifier);
return;
}
final String flagKey = formatFlagKey(identifier);
final Object previousFeatureConfig = store != null ? store.get(flagKey) : cache.get(flagKey);
if (cachePreviousFeatureConfigVersion && previousFeatureConfig != null) {
final String previousFlagKey = formatPrevFlagKey(identifier);
if (store != null) {
store.set(previousFlagKey, previousFeatureConfig);
cache.delete(previousFlagKey);
log.debug("Flag {} successfully stored and cache invalidated", previousFlagKey);
} else {
cache.set(previousFlagKey, previousFeatureConfig);
}
log.debug("Flag {} successfully stored", previousFlagKey);
}
if (store != null) {
store.set(flagKey, featureConfig);
cache.delete(flagKey);
} else {
cache.set(flagKey, featureConfig);
}
log.debug("Flag {} successfully stored", identifier);
if (callback != null) {
callback.onFlagStored(identifier);
}
}
@Override
public void setSegment(@NonNull String identifier, @NonNull Segment segment) {
if (isSegmentOutdated(identifier, segment)) {
log.debug("Segment {} already exists", identifier);
return;
}
// Sort the serving rules before storing the segment
sortSegmentServingRules(segment);
final String segmentKey = formatSegmentKey(identifier);
if (store != null) {
store.set(segmentKey, segment);
cache.delete(segmentKey);
log.debug("Segment {} successfully stored and cache invalidated", identifier);
} else {
cache.set(segmentKey, segment);
log.debug("Segment {} successfully cached", identifier);
}
if (callback != null) {
callback.onSegmentStored(identifier);
}
}
@Override
public void deleteFlag(@NonNull String identifier) {
final String flagKey = this.formatFlagKey(identifier);
final String pflgKey = this.formatPrevFlagKey(identifier);
if (store != null) {
if (cachePreviousFeatureConfigVersion) {
store.delete(pflgKey);
}
store.delete(flagKey);
log.debug("Flag {} successfully deleted from store", identifier);
}
if (cachePreviousFeatureConfigVersion) {
this.cache.delete(pflgKey);
}
this.cache.delete(flagKey);
log.debug("Flag {} successfully deleted from cache", identifier);
if (callback != null) {
callback.onFlagDeleted(identifier);
}
}
@Override
public void deleteSegment(@NonNull String identifier) {
final String segmentKey = this.formatSegmentKey(identifier);
if (store != null) {
store.delete(segmentKey);
log.debug("Segment {} successfully deleted from store", identifier);
}
this.cache.delete(segmentKey);
log.debug("Segment {} successfully deleted from cache", identifier);
if (callback != null) {
callback.onSegmentDeleted(identifier);
}
}
protected boolean isFlagOutdated(
@NonNull String identifier, @NonNull FeatureConfig newFeatureConfig) {
final Optional oldFlag = getFlag(identifier, false);
if (oldFlag.isPresent()) {
final FeatureConfig flag = oldFlag.get();
if (flag.getVersion() != null && newFeatureConfig.getVersion() != null)
return flag.getVersion() >= newFeatureConfig.getVersion();
}
log.debug("Flag is outdated {}", identifier);
return false;
}
protected boolean isSegmentOutdated(@NonNull String identifier, @NonNull Segment newSegment) {
final Optional oldSegment = getSegment(identifier, false);
if (oldSegment.isPresent()) {
final Segment segment = oldSegment.get();
if (segment.getVersion() != null && newSegment.getVersion() != null)
return segment.getVersion() >= newSegment.getVersion();
}
log.debug("Segment is outdated {}", identifier);
return false;
}
private void sortSegmentServingRules(Segment segment) {
if (segment.getServingRules() != null && segment.getServingRules().size() > 1) {
segment.getServingRules().sort(Comparator.comparing(GroupServingRule::getPriority));
}
}
@NonNull
protected String formatFlagKey(@NonNull String identifier) {
return String.format("flags/%s", identifier);
}
protected String formatPrevFlagKey(@NonNull String identifier) {
return String.format("previous/%s", identifier);
}
@NonNull
protected String formatSegmentKey(@NonNull String identifier) {
return String.format("segments/%s", identifier);
}
@Override
public void close() {
if (store != null) {
store.close();
log.debug("store closed");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy