package me.dinowernli.grpc.polyglot.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.protobuf.util.JsonFormat;
import polyglot.ConfigProto.Configuration;
import polyglot.ConfigProto.ConfigurationSet;
import polyglot.ConfigProto.OutputConfiguration.Destination;
/** A utility which manipulating and reading a single {@link ConfigurationSet}. */
public class ConfigurationLoader {
private static final String DEFAULT_FILE_NAME = "config.pb.json";
private static final String DEFAULT_LOCATION = ".polyglot";
/** If this is absent, we hand out default instances of configs. */
private final Optional<ConfigurationSet> configSet;
/** Optional overrides for the configuration. */
private final Optional<CommandLineArgs> overrides;
/**
* Returns a {@link ConfigurationLoader} backed by a {@link ConfigurationSet} in the current
* user's home directory. If no such file exists, it falls back to a default config.
*/
public static ConfigurationLoader forDefaultConfigSet() {
String homeDirectory = System.getProperty("user.home");
Path defaultLocation = Paths.get(homeDirectory, DEFAULT_LOCATION, DEFAULT_FILE_NAME);
if (Files.exists(defaultLocation)) {
return ConfigurationLoader.forFile(defaultLocation);
} else {
return new ConfigurationLoader(Optional.empty(), Optional.empty());
}
}
/** Constructs a {@link ConfigurationLoader} from an explicit {@link ConfigurationSet}. */
public static ConfigurationLoader forConfigSet(ConfigurationSet configSet) {
return new ConfigurationLoader(Optional.of(configSet), Optional.empty() /* overrides */);
}
/** Returns a loader backed by config set obtained from the supplied file. */
public static ConfigurationLoader forFile(Path configFile) {
try {
ConfigurationSet.Builder configSetBuilder = ConfigurationSet.newBuilder();
String fileContent = Joiner.on('\n').join(Files.readAllLines(configFile));
JsonFormat.parser().merge(fileContent, configSetBuilder);
return ConfigurationLoader.forConfigSet(configSetBuilder.build());
} catch (IOException e) {
throw new RuntimeException("Unable to read config file: " + configFile.toString(), e);
}
}
@VisibleForTesting
ConfigurationLoader(Optional<ConfigurationSet> configSet, Optional<CommandLineArgs> overrides) {
this.configSet = configSet;
this.overrides = overrides;
}
/** Returns a new instance of {@link ConfigurationLoader} with the supplied overrides. */
public ConfigurationLoader withOverrides(CommandLineArgs overrides) {
return new ConfigurationLoader(configSet, Optional.of(overrides));
}
/** Returns the default configuration from the loaded configuration set. */
public Configuration getDefaultConfiguration() {
return applyOverrides(getDefaultConfigurationInternal());
}
/** Returns a config with the supplied name and throws if no such config is found. */
public Configuration getNamedConfiguration(String name) {
return applyOverrides(getNamedConfigurationInternal(name));
}
private Configuration getDefaultConfigurationInternal() {
if (isEmptyConfig()) {
return Configuration.getDefaultInstance();
}
if (configSet.get().getConfigurationsList().isEmpty()) {
throw new IllegalStateException("No configs present in config set");
}
return configSet.get().getConfigurations(0);
}
private Configuration getNamedConfigurationInternal(String name) {
Preconditions.checkState(!isEmptyConfig(), "Cannot load named config with a config set");
return configSet.get().getConfigurationsList().stream()
.filter(config -> config.getName().equals(name))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name));
}
/** Returns the {@link Configuration} with overrides, if any, applied to it. */
private Configuration applyOverrides(Configuration configuration) {
if (!overrides.isPresent()) {
return configuration;
}
Configuration.Builder resultBuilder = configuration.toBuilder();
if (overrides.get().useTls().isPresent()) {
resultBuilder.getCallConfigBuilder().setUseTls(overrides.get().useTls().get());
}
if (overrides.get().outputFilePath().isPresent()) {
resultBuilder.getOutputConfigBuilder().setDestination(Destination.FILE);
resultBuilder.getOutputConfigBuilder().setFilePath(
overrides.get().outputFilePath().get().toString());
}
if (!overrides.get().additionalProtocIncludes().isEmpty()) {
List<String> additionalIncludes = new ArrayList<>();
for (Path path : overrides.get().additionalProtocIncludes()) {
additionalIncludes.add(path.toString());
}
resultBuilder.getProtoConfigBuilder().addAllIncludePaths(additionalIncludes);
}
if (overrides.get().protoDiscoveryRoot().isPresent()) {
resultBuilder.getProtoConfigBuilder().setProtoDiscoveryRoot(
overrides.get().protoDiscoveryRoot().get().toString());
}
if (overrides.get().getRpcDeadlineMs().isPresent()) {
resultBuilder.getCallConfigBuilder().setDeadlineMs(overrides.get().getRpcDeadlineMs().get());
}
if (overrides.get().tlsCaCertPath().isPresent()) {
resultBuilder.getCallConfigBuilder().setTlsCaCertPath(
overrides.get().tlsCaCertPath().get().toString());
}
return resultBuilder.build();
}
/** Returns false iff this is backed by a real config set (rather than the special empty one). */
private boolean isEmptyConfig() {
return !configSet.isPresent();
}
}