
dev.openfeature.sdk.ProviderRepository Maven / Gradle / Ivy
package dev.openfeature.sdk;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class ProviderRepository {
private final Map providers = new ConcurrentHashMap<>();
private final AtomicReference defaultProvider = new AtomicReference<>(new NoOpProvider());
private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {
final Thread thread = new Thread(runnable);
thread.setDaemon(true);
return thread;
});
/**
* Return the default provider.
*/
public FeatureProvider getProvider() {
return defaultProvider.get();
}
/**
* Fetch a provider for a domain. If not found, return the default.
*
* @param domain The domain to look for.
* @return A named {@link FeatureProvider}
*/
public FeatureProvider getProvider(String domain) {
return Optional.ofNullable(domain).map(this.providers::get).orElse(this.defaultProvider.get());
}
public List getDomainsForProvider(FeatureProvider provider) {
return providers.entrySet().stream()
.filter(entry -> entry.getValue().equals(provider))
.map(entry -> entry.getKey()).collect(Collectors.toList());
}
public Set getAllBoundDomains() {
return providers.keySet();
}
public boolean isDefaultProvider(FeatureProvider provider) {
return this.getProvider().equals(provider);
}
/**
* Set the default provider.
*/
public void setProvider(FeatureProvider provider,
Consumer afterSet,
Consumer afterInit,
Consumer afterShutdown,
BiConsumer afterError,
boolean waitForInit) {
if (provider == null) {
throw new IllegalArgumentException("Provider cannot be null");
}
prepareAndInitializeProvider(null, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit);
}
/**
* Add a provider for a domain.
*
* @param domain The domain to bind the provider to.
* @param provider The provider to set.
* @param waitForInit When true, wait for initialization to finish, then returns.
* Otherwise, initialization happens in the background.
*/
public void setProvider(String domain,
FeatureProvider provider,
Consumer afterSet,
Consumer afterInit,
Consumer afterShutdown,
BiConsumer afterError,
boolean waitForInit) {
if (provider == null) {
throw new IllegalArgumentException("Provider cannot be null");
}
if (domain == null) {
throw new IllegalArgumentException("domain cannot be null");
}
prepareAndInitializeProvider(domain, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit);
}
private void prepareAndInitializeProvider(String domain,
FeatureProvider newProvider,
Consumer afterSet,
Consumer afterInit,
Consumer afterShutdown,
BiConsumer afterError,
boolean waitForInit) {
if (!isProviderRegistered(newProvider)) {
// only run afterSet if new provider is not already attached
afterSet.accept(newProvider);
}
// provider is set immediately, on this thread
FeatureProvider oldProvider = domain != null
? this.providers.put(domain, newProvider)
: this.defaultProvider.getAndSet(newProvider);
if (waitForInit) {
initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider);
} else {
taskExecutor.submit(() -> {
// initialization happens in a different thread if we're not waiting it
initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider);
});
}
}
private void initializeProvider(FeatureProvider newProvider,
Consumer afterInit,
Consumer afterShutdown,
BiConsumer afterError,
FeatureProvider oldProvider) {
try {
if (ProviderState.NOT_READY.equals(newProvider.getState())) {
newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext());
afterInit.accept(newProvider);
}
shutDownOld(oldProvider, afterShutdown);
} catch (OpenFeatureError e) {
log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e);
afterError.accept(newProvider, e);
} catch (Exception e) {
log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e);
afterError.accept(newProvider, new GeneralError(e));
}
}
private void shutDownOld(FeatureProvider oldProvider, Consumer afterShutdown) {
if (!isProviderRegistered(oldProvider)) {
shutdownProvider(oldProvider);
afterShutdown.accept(oldProvider);
}
}
/**
* Helper to check if provider is already known (registered).
* @param provider provider to check for registration
* @return boolean true if already registered, false otherwise
*/
private boolean isProviderRegistered(FeatureProvider provider) {
return provider != null
&& (this.providers.containsValue(provider) || this.defaultProvider.get().equals(provider));
}
private void shutdownProvider(FeatureProvider provider) {
taskExecutor.submit(() -> {
try {
provider.shutdown();
} catch (Exception e) {
log.error("Exception when shutting down feature provider {}", provider.getClass().getName(), e);
}
});
}
/**
* Shuts down this repository which includes shutting down all FeatureProviders
* that are registered,
* including the default feature provider.
*/
public void shutdown() {
Stream
.concat(Stream.of(this.defaultProvider.get()), this.providers.values().stream())
.distinct()
.forEach(this::shutdownProvider);
this.providers.clear();
taskExecutor.shutdown();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy