io.harness.cf.client.api.InnerClient 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.JsonObject;
import io.harness.cf.client.common.SdkCodes;
import io.harness.cf.client.connector.*;
import io.harness.cf.client.dto.Message;
import io.harness.cf.client.dto.Target;
import io.harness.cf.model.FeatureConfig;
import io.harness.cf.model.FeatureSnapshot;
import io.harness.cf.model.Variation;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class InnerClient
implements AutoCloseable,
FlagEvaluateCallback,
AuthCallback,
PollerCallback,
RepositoryCallback,
MetricsCallback,
Updater {
enum Processor {
POLL,
STREAM,
METRICS,
}
private Connector connector;
private Evaluation evaluator;
private Repository repository;
private BaseConfig options;
private AuthService authService;
private PollingProcessor pollProcessor;
private MetricsProcessor metricsProcessor;
private UpdateProcessor updateProcessor;
private boolean initialized = false;
private boolean closing = false;
private boolean failure = false;
private boolean pollerReady = false;
private boolean streamReady = false;
private boolean metricReady = false;
private final ConcurrentHashMap>> events =
new ConcurrentHashMap<>();
public InnerClient(@NonNull final String sdkKey) {
this(sdkKey, BaseConfig.builder().build());
}
@Deprecated
public InnerClient(@NonNull final String sdkKey, @NonNull final Config options) {
HarnessConfig config =
HarnessConfig.builder()
.configUrl(options.getConfigUrl())
.eventUrl(options.getEventUrl())
.connectionTimeout(options.getConnectionTimeout())
.readTimeout(options.readTimeout)
.writeTimeout(options.getWriteTimeout())
.build();
HarnessConnector harnessConnector = new HarnessConnector(sdkKey, config);
setUp(harnessConnector, options);
}
public InnerClient(@NonNull final String sdkKey, @NonNull final BaseConfig options) {
HarnessConfig config = HarnessConfig.builder().build();
HarnessConnector harnessConnector = new HarnessConnector(sdkKey, config);
setUp(harnessConnector, options);
}
public InnerClient(@NonNull final Connector connector) {
this(connector, BaseConfig.builder().build());
}
public InnerClient(@NonNull Connector connector, @NonNull final BaseConfig options) {
setUp(connector, options);
}
protected void setUp(@NonNull final Connector connector, @NonNull final BaseConfig options) {
this.options = options;
log.info("Starting SDK client with configuration: {}", this.options);
this.connector = connector;
this.connector.setOnUnauthorized(this::onUnauthorized);
// initialization
repository =
new StorageRepository(
options.getCache(), options.getStore(), this, options.isEnableFeatureSnapshot());
evaluator = new Evaluator(repository, options);
authService = new AuthService(this.connector, options.getPollIntervalInSeconds(), this);
pollProcessor =
new PollingProcessor(this.connector, repository, options.getPollIntervalInSeconds(), this);
metricsProcessor =
new MetricsProcessor(
this.connector, this.options, this, connector.getShouldFlushAnalyticsOnClose());
updateProcessor = new UpdateProcessor(this.connector, this.repository, this);
// start with authentication
authService.start();
}
protected void onUnauthorized() {
if (closing) {
return;
}
log.info("Unauthorized event received. Stopping all processors and run auth service");
pollProcessor.stop();
if (options.isStreamEnabled()) {
updateProcessor.stop();
}
if (options.isAnalyticsEnabled()) {
metricsProcessor.stop();
}
authService.start();
log.info("re-auth started");
}
@Override
public void onAuthSuccess() {
log.debug("SDK successfully logged in");
if (closing) {
return;
}
log.debug("start poller processor");
pollProcessor.start();
if (options.isStreamEnabled()) {
log.debug("Stream enabled, start update processor");
updateProcessor.start();
}
if (options.isAnalyticsEnabled()) {
log.debug("Analytics enabled, start metrics processor");
metricsProcessor.start();
}
}
@Override
public void onPollerReady() {
initialize(Processor.POLL);
}
@Override
public void onPollerError(@NonNull final Exception exc) {
log.error("PollerProcessor exception", exc);
}
@Override
public void onPollerFailed(@NonNull final Exception exc) {
log.error("PollerProcessor failed while initializing, exception: ", exc);
}
@Override
public void onFlagStored(@NonNull final String identifier) {
notifyConsumers(Event.CHANGED, identifier);
}
@Override
public void onFlagDeleted(@NonNull final String identifier) {
notifyConsumers(Event.CHANGED, identifier);
}
@Override
public void onSegmentStored(@NonNull final String identifier) {
repository.findFlagsBySegment(identifier).forEach(s -> notifyConsumers(Event.CHANGED, s));
}
@Override
public void onSegmentDeleted(@NonNull final String identifier) {
repository.findFlagsBySegment(identifier).forEach(s -> notifyConsumers(Event.CHANGED, s));
}
@Override
public void onMetricsReady() {
initialize(Processor.METRICS);
}
@Override
public void onMetricsError(@NonNull final String error) {
log.error("Metrics error: {}", error);
}
@Override
public synchronized void onMetricsFailure() {
failure = true;
notifyAll();
}
@Override
public void onConnected() {
SdkCodes.infoStreamConnected();
if (pollProcessor.isRunning()) {
// refresh any flags that may have gotten out of sync if the SSE connection was down
pollProcessor.retrieveAll();
pollProcessor.stop();
}
}
@Override
public void onDisconnected(String reason) {
SdkCodes.warnStreamDisconnected(reason);
if (!closing && !pollProcessor.isRunning()) {
log.debug("onDisconnected triggered, starting poller to get latest flags");
pollProcessor.close();
pollProcessor =
new PollingProcessor(connector, repository, options.getPollIntervalInSeconds(), this);
pollProcessor.start();
if (updateProcessor != null && options.isStreamEnabled()) {
updateProcessor.restart();
}
} else {
log.debug(
"Poller already running [closing={} interval={}]",
closing,
options.getPollIntervalInSeconds());
log.debug("SSE disconnect detected - asking poller to refresh flags");
if (!closing) {
pollProcessor.retrieveAll();
}
}
}
@Override
public void onReady() {
log.debug("onReady triggered");
initialize(Processor.STREAM);
}
public synchronized void onFailure(@NonNull final String error) {
SdkCodes.warnAuthFailedSrvDefaults(error);
failure = true;
notifyAll();
}
@Override
public void update(@NonNull final Message message) {
log.debug("update triggered [event={}] ", message.getEvent());
updateProcessor.update(message);
}
public void update(@NonNull final Message message, final boolean manual) {
log.debug("update triggered [event={} manual={}] ", message.getEvent(), manual);
if (options.isStreamEnabled() && manual) {
log.warn(
"You have run update method manually with the stream enabled. Please turn off the stream in this case.");
}
update(message);
}
private synchronized void initialize(@NonNull final Processor processor) {
if (initialized || closing) {
log.debug("client is already initialized {} or closing {}", initialized, closing);
return;
}
switch (processor) {
case POLL:
pollerReady = true;
log.debug("PollingProcessor ready");
break;
case STREAM:
streamReady = true;
log.debug("Updater ready");
break;
case METRICS:
metricReady = true;
log.debug("MetricsProcessor ready");
break;
}
if ((options.isStreamEnabled() && !streamReady)
|| (options.isAnalyticsEnabled() && !this.metricReady)
|| (!this.pollerReady)) {
return;
}
initialized = true;
notifyAll();
notifyConsumers(Event.READY, null);
SdkCodes.infoSdkInitOk();
}
protected void notifyConsumers(@NonNull final Event event, final String value) {
CopyOnWriteArrayList> consumers = events.get(event);
if (consumers != null && !consumers.isEmpty()) {
consumers.forEach(c -> c.accept(value));
}
}
/** if waitForInitialization is used then on(READY) will never be triggered */
public synchronized void waitForInitialization()
throws InterruptedException, FeatureFlagInitializeException {
while (!initialized) {
log.info("Wait for initialization to finish");
wait(5000);
if (failure) {
log.error("Failure while initializing SDK!");
throw new FeatureFlagInitializeException();
}
}
}
public List getFeatureSnapshots() {
return getFeatureSnapshots("");
}
public List getFeatureSnapshots(String prefix) {
if (!options.isEnableFeatureSnapshot()) {
log.debug("FeatureSnapshot disabled, snapshot will contain only current version.");
}
List identifiers = repository.getAllFeatureIdentifiers(prefix);
List snapshots = new LinkedList<>();
for (String identifier : identifiers) {
FeatureSnapshot snapshot = getFeatureSnapshot(identifier);
snapshots.add(snapshot);
}
return snapshots;
}
public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) {
if (!options.isEnableFeatureSnapshot()) {
log.debug("FeatureSnapshot disabled, snapshot will contain only current version.");
}
return repository.getFeatureSnapshot(identifier);
}
public void on(@NonNull final Event event, @NonNull final Consumer consumer) {
final CopyOnWriteArrayList> consumers =
events.getOrDefault(event, new CopyOnWriteArrayList<>());
consumers.add(consumer);
events.put(event, consumers);
}
public void off() {
events.clear();
}
public void off(@NonNull final Event event) {
events.get(event).clear();
}
public void off(@NonNull final Event event, @NonNull final Consumer consumer) {
events.get(event).removeIf(next -> next == consumer);
}
public boolean boolVariation(
@NonNull final String identifier, final Target target, final boolean defaultValue) {
return evaluator.boolVariation(identifier, target, defaultValue, this);
}
public String stringVariation(
@NonNull final String identifier, final Target target, @NonNull final String defaultValue) {
return evaluator.stringVariation(identifier, target, defaultValue, this);
}
public double numberVariation(
@NonNull final String identifier, final Target target, final double defaultValue) {
return evaluator.numberVariation(identifier, target, defaultValue, this);
}
public JsonObject jsonVariation(
@NonNull String identifier, Target target, @NonNull JsonObject defaultValue) {
return evaluator.jsonVariation(identifier, target, defaultValue, this);
}
@Override
public void processEvaluation(
@NonNull FeatureConfig featureConfig, Target target, @NonNull Variation variation) {
if (this.options.isAnalyticsEnabled()) {
metricsProcessor.registerEvaluation(target, featureConfig.getFeature(), variation);
}
}
public void close() {
log.info("Closing the client");
closing = true;
// Mark the connector as shutting down to stop request retries from taking place. The
// connections will eventually
// be evicted when the connector is closed, but this ensures that if metrics are flushed when
// closed then it won't attempt to retry if the first request fails.
connector.setIsShuttingDown();
off();
authService.close();
repository.close();
pollProcessor.close();
updateProcessor.close();
metricsProcessor.close();
connector.close();
log.info("All resources released and client closed");
}
/* Package private */
BaseConfig getOptions() {
return options;
}
PollingProcessor getPollProcessor() {
return pollProcessor;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy