io.scalecube.config.keyvalue.KeyValueConfigSource Maven / Gradle / Ivy
package io.scalecube.config.keyvalue;
import io.scalecube.config.ConfigProperty;
import io.scalecube.config.ConfigSourceNotAvailableException;
import io.scalecube.config.source.ConfigSource;
import io.scalecube.config.source.LoadedConfigProperty;
import io.scalecube.config.utils.ThrowableUtil;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
* Generic key-value config source. Communicates with concrete config data source (mongodb, redis,
* zookeeper) using injectable {@link #repository}.
*/
public class KeyValueConfigSource implements ConfigSource {
private static final Logger LOGGER = System.getLogger(KeyValueConfigSource.class.getName());
private static final ThreadFactory threadFactory;
static {
threadFactory =
r -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("keyvalue-config-executor");
thread.setUncaughtExceptionHandler(
(t, e) -> LOGGER.log(Level.ERROR, "Exception occurred", e));
return thread;
};
}
private static final Executor executor = Executors.newCachedThreadPool(threadFactory);
private final KeyValueConfigRepository repository;
private final Duration repositoryTimeout;
private final List configNames; // calculated field
private KeyValueConfigSource(Builder builder) {
this.repository = builder.repository;
this.repositoryTimeout = builder.repositoryTimeout;
this.configNames = configureConfigNames(builder.groupList, builder.collectionName);
}
private static List configureConfigNames(
List groupList, String collectionName) {
List result = new ArrayList<>();
result.addAll(groupList);
result.add(null); // by default 'root' group is always added
return result.stream()
.map(input -> new KeyValueConfigName(input, collectionName))
.collect(Collectors.toList());
}
public static Builder withRepository(KeyValueConfigRepository repository) {
return new Builder(repository);
}
public static Builder withRepository(KeyValueConfigRepository repository, String collectionName) {
return new Builder(repository, collectionName);
}
@Override
public Map loadConfig() {
List>> futureList =
configNames.stream().map(this::loadConfig).collect(Collectors.toList());
CompletableFuture allResults =
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
CompletableFuture>> joinedFuture =
allResults.thenApply(
input -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList()));
List> resultList;
try {
resultList = joinedFuture.get(repositoryTimeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
throw ThrowableUtil.propagate(e.getCause());
} catch (TimeoutException e) {
String message =
String.format("TimeoutException after '%s' millis", repositoryTimeout.toMillis());
throw new ConfigSourceNotAvailableException(message, e);
} catch (InterruptedException e) {
Thread.interrupted();
throw ThrowableUtil.propagate(e);
}
return resultList.stream()
.flatMap(Collection::stream)
.filter(i -> !i.getDisabled())
.collect(
Collector.of(
(Supplier>) TreeMap::new,
(map, i) -> {
String origin = i.getConfigName().getQualifiedName();
String name = i.getPropName();
String value = i.getPropValue();
map.putIfAbsent(
name,
LoadedConfigProperty.withNameAndValue(name, value).origin(origin).build());
},
(map1, map2) -> map1));
}
private CompletableFuture> loadConfig(KeyValueConfigName configName) {
return CompletableFuture.supplyAsync(
() -> {
List result;
try {
result = repository.findAll(configName);
} catch (Exception e) {
LOGGER.log(
Level.WARNING,
"Exception at {0}.findAll({1})",
repository.getClass().getSimpleName(),
configName,
e);
result = Collections.emptyList();
}
return result;
},
executor);
}
public static class Builder {
private static final Duration DEFAULT_REPOSITORY_TIMEOUT = Duration.ofSeconds(3);
private static final String DEFAULT_COLLECTION_NAME = "KeyValueConfigSource";
private final KeyValueConfigRepository repository;
private final String collectionName;
private List groupList = new ArrayList<>();
private Duration repositoryTimeout = DEFAULT_REPOSITORY_TIMEOUT;
private Builder(KeyValueConfigRepository repository) {
this(repository, DEFAULT_COLLECTION_NAME);
}
private Builder(KeyValueConfigRepository repository, String collectionName) {
this.repository = Objects.requireNonNull(repository);
this.collectionName = Objects.requireNonNull(collectionName);
}
public Builder groups(String... groups) {
this.groupList = Arrays.asList(groups);
return this;
}
public Builder groupList(List groupList) {
this.groupList = groupList;
return this;
}
public Builder repositoryTimeout(Duration repositoryTimeout) {
this.repositoryTimeout = repositoryTimeout;
return this;
}
public KeyValueConfigSource build() {
return new KeyValueConfigSource(this);
}
}
}