Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.launchdarkly.sdk.server.integrations.PollingDataSourceBuilder;
import com.launchdarkly.sdk.server.integrations.ServiceEndpointsBuilder;
import com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder;
import com.launchdarkly.sdk.server.integrations.DataSystemModes;
import com.launchdarkly.sdk.server.integrations.WrapperInfoBuilder;
import com.launchdarkly.sdk.server.interfaces.HttpAuthentication;
import com.launchdarkly.sdk.server.subsystems.BigSegmentStore;
Expand Down Expand Up @@ -476,4 +477,21 @@ public static PluginsConfigurationBuilder plugins() {
* @since 7.1.0
*/
public static WrapperInfoBuilder wrapperInfo() { return new WrapperInfoBuilderImpl(); }

/**
* This API is under active development. Do not use.
*
* Returns a set of builder options for configuring the SDK data system. When the data system configuration
* is used it overrides {@link LDConfig.Builder#dataSource(ComponentConfigurer)} and
* {@link LDConfig.Builder#dataStore(ComponentConfigurer)} in the configuration.
* <p>
* This class is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
* It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
* </p>
*
* @return a configuration builder
*/
public static DataSystemModes dataSystem() {
return new DataSystemModes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ private boolean hasFlagChangeEventListeners() {
return flagChangeEventNotifier.hasListeners();
}

void addFlagChangeListener(FlagChangeListener listener) {
flagChangeEventNotifier.register(listener);
}

void removeFlagChangeListener(FlagChangeListener listener) {
flagChangeEventNotifier.unregister(listener);
}

private void sendChangeEvents(Iterable<KindAndKey> affectedItems) {
for (KindAndKey item: affectedItems) {
if (item.kind == FEATURES) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider;
import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider;
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.DataKind;
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ItemDescriptor;
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.KeyedItems;

import java.util.concurrent.Future;

/**
* Internal interface for the data system abstraction.
* <p>
* This interface is package-private and should not be used by application code.
* <p>
* This class is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
* It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
*/
interface DataSystem {
/**
* Returns the read-only store interface.
*
* @return the read-only store
*/
ReadOnlyStore getStore();

/**
* Starts the data system.
*
* @return a Future that completes when initialization is complete
*/
Future<Void> start();

/**
* Returns whether the data system has been initialized.
*
* @return true if initialized
*/
boolean isInitialized();

/**
* Returns the flag change notifier interface.
*
* @return the flag change notifier
*/
FlagChangeNotifier getFlagChanged();

/**
* Returns the data source status provider.
*
* @return the data source status provider
*/
DataSourceStatusProvider getDataSourceStatusProvider();

/**
* Returns the data store status provider.
*
* @return the data store status provider
*/
DataStoreStatusProvider getDataStoreStatusProvider();
}

/**
* Internal interface for read-only access to a data store.
* <p>
* This interface is package-private and should not be used by application code.
*/
interface ReadOnlyStore {
/**
* Retrieves an item from the specified collection, if available.
* <p>
* If the item has been deleted and the store contains a placeholder, it should
* return that placeholder rather than null.
*
* @param kind specifies which collection to use
* @param key the unique key of the item within that collection
* @return a versioned item that contains the stored data (or placeholder for deleted data);
* null if the key is unknown
*/
ItemDescriptor get(DataKind kind, String key);

/**
* Retrieves all items from the specified collection.
* <p>
* If the store contains placeholders for deleted items, it should include them in
* the results, not filter them out.
*
* @param kind specifies which collection to use
* @return a collection of key-value pairs; the ordering is not significant
*/
KeyedItems<ItemDescriptor> getAll(DataKind kind);

/**
* Checks whether this store has been initialized with any data yet.
*
* @return true if the store contains data
*/
boolean isInitialized();
}

/**
* Internal interface for flag change notifications.
* <p>
* This interface is package-private and should not be used by application code.
*/
interface FlagChangeNotifier {
/**
* Adds a listener for flag change events.
*
* @param listener the listener to add
*/
void addFlagChangeListener(com.launchdarkly.sdk.server.interfaces.FlagChangeListener listener);

/**
* Removes a listener for flag change events.
*
* @param listener the listener to remove
*/
void removeFlagChangeListener(com.launchdarkly.sdk.server.interfaces.FlagChangeListener listener);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.logging.LDLogger;
import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider;
import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider;
import com.launchdarkly.sdk.server.interfaces.FlagChangeEvent;
import com.launchdarkly.sdk.server.interfaces.FlagChangeListener;
import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer;
import com.launchdarkly.sdk.server.subsystems.DataSource;
import com.launchdarkly.sdk.server.subsystems.DataStore;
import com.launchdarkly.sdk.server.subsystems.LoggingConfiguration;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.Future;

/**
* Internal implementation of the FDv1 data system.
* <p>
* This class is package-private and should not be used by application code.
*/
final class FDv1DataSystem implements DataSystem, Closeable {
private final DataSource dataSource;
private final DataStore dataStore;
private final ReadOnlyStore store;
private final FlagChangeNotifier flagChanged;
private final DataSourceStatusProvider dataSourceStatusProvider;
private final DataStoreStatusProvider dataStoreStatusProvider;
private boolean disposed = false;

/**
* Testing access to internal components.
*/
static final class TestingAccess {
final DataSource dataSource;

TestingAccess(DataSource dataSource) {
this.dataSource = dataSource;
}
}

final TestingAccess testing;

/**
* Gets the underlying data store. This is needed for the evaluator.
* @return the underlying data store
*/
DataStore getUnderlyingStore() {
return dataStore;
}

private FDv1DataSystem(
DataStore store,
DataStoreStatusProvider dataStoreStatusProvider,
DataSourceStatusProvider dataSourceStatusProvider,
DataSource dataSource,
FlagChangeNotifier flagChanged
) {
this.dataStoreStatusProvider = dataStoreStatusProvider;
this.dataSourceStatusProvider = dataSourceStatusProvider;
this.store = new ReadonlyStoreFacade(store);
this.flagChanged = flagChanged;
this.dataSource = dataSource;
this.dataStore = store;
this.testing = new TestingAccess(dataSource);
}

/**
* Creates a new FDv1DataSystem instance.
*
* @param logger the logger
* @param config the SDK configuration
* @param clientContext the client context
* @param logConfig the logging configuration
* @return a new FDv1DataSystem instance
*/
static FDv1DataSystem create(
LDLogger logger,
LDConfig config,
ClientContextImpl clientContext,
LoggingConfiguration logConfig
) {
DataStoreUpdatesImpl dataStoreUpdates = new DataStoreUpdatesImpl(
EventBroadcasterImpl.forDataStoreStatus(clientContext.sharedExecutor, logger));

DataStore dataStore = (config.dataStore == null ? Components.inMemoryDataStore() : config.dataStore)
.build(clientContext.withDataStoreUpdateSink(dataStoreUpdates));

DataStoreStatusProvider dataStoreStatusProvider = new DataStoreStatusProviderImpl(dataStore, dataStoreUpdates);

// Create a single flag change broadcaster to be shared between DataSourceUpdatesImpl and FlagTrackerImpl
EventBroadcasterImpl<FlagChangeListener, FlagChangeEvent> flagChangeBroadcaster =
EventBroadcasterImpl.forFlagChangeEvents(clientContext.sharedExecutor, logger);

// Create a single data source status broadcaster to be shared between DataSourceUpdatesImpl and DataSourceStatusProviderImpl
EventBroadcasterImpl<DataSourceStatusProvider.StatusListener, DataSourceStatusProvider.Status> dataSourceStatusBroadcaster =
EventBroadcasterImpl.forDataSourceStatus(clientContext.sharedExecutor, logger);

DataSourceUpdatesImpl dataSourceUpdates = new DataSourceUpdatesImpl(
dataStore,
dataStoreStatusProvider,
flagChangeBroadcaster,
dataSourceStatusBroadcaster,
clientContext.sharedExecutor,
logConfig.getLogDataSourceOutageAsErrorAfter(),
logger
);

ComponentConfigurer<DataSource> dataSourceFactory = config.offline
? Components.externalUpdatesOnly()
: (config.dataSource == null ? Components.streamingDataSource() : config.dataSource);
DataSource dataSource = dataSourceFactory.build(clientContext.withDataSourceUpdateSink(dataSourceUpdates));
DataSourceStatusProvider dataSourceStatusProvider = new DataSourceStatusProviderImpl(
dataSourceStatusBroadcaster,
dataSourceUpdates);

FlagChangeNotifier flagChanged = new FlagChangedFacade(dataSourceUpdates);

return new FDv1DataSystem(
dataStore,
dataStoreStatusProvider,
dataSourceStatusProvider,
dataSource,
flagChanged
);
}

@Override
public ReadOnlyStore getStore() {
return store;
}

@Override
public Future<Void> start() {
return dataSource.start();
}

@Override
public boolean isInitialized() {
return dataSource.isInitialized();
}

@Override
public FlagChangeNotifier getFlagChanged() {
return flagChanged;
}

@Override
public DataSourceStatusProvider getDataSourceStatusProvider() {
return dataSourceStatusProvider;
}

@Override
public DataStoreStatusProvider getDataStoreStatusProvider() {
return dataStoreStatusProvider;
}

@Override
public void close() throws IOException {
if (disposed) {
return;
}
try {
if (dataSource instanceof Closeable) {
((Closeable) dataSource).close();
}
if (dataStore instanceof Closeable) {
((Closeable) dataStore).close();
}
// DataSourceUpdatesImpl doesn't implement Closeable
} finally {
disposed = true;
}
}
}

Loading