Move YamlUtils to be under google.registry.util package

This makes it simpler to package google.registry.util as a separate project in
Gradle that can be depended upon by the proxy package. Currently the proxy
package depends on both google.registry.util and google.registry.config.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=221450085
This commit is contained in:
jianglai 2018-11-14 08:41:31 -08:00
parent e51cf3e9c7
commit c0239b0a07
6 changed files with 29 additions and 27 deletions

View file

@ -23,6 +23,5 @@ java_library(
"@javax_inject",
"@joda_time",
"@org_joda_money",
"@org_yaml_snakeyaml",
],
)

View file

@ -16,9 +16,11 @@ package google.registry.config;
import static com.google.common.base.Suppliers.memoize;
import static google.registry.config.ConfigUtils.makeUrl;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
@ -29,6 +31,7 @@ import dagger.Provides;
import google.registry.util.RandomStringGenerator;
import google.registry.util.StringGenerator;
import google.registry.util.TaskQueueUtils;
import google.registry.util.YamlUtils;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.net.URI;
@ -56,6 +59,10 @@ import org.joda.time.Duration;
*/
public final class RegistryConfig {
private static final String ENVIRONMENT_CONFIG_FORMAT = "files/nomulus-config-%s.yaml";
private static final String YAML_CONFIG_PROD =
readResourceUtf8(RegistryConfig.class, "files/default-config.yaml");
/** Dagger qualifier for configuration settings. */
@Qualifier
@Retention(RUNTIME)
@ -64,6 +71,22 @@ public final class RegistryConfig {
String value() default "";
}
/**
* Loads the {@link RegistryConfigSettings} POJO from the YAML configuration files.
*
* <p>The {@code default-config.yaml} file in this directory is loaded first, and a fatal error is
* thrown if it cannot be found or if there is an error parsing it. Separately, the
* environment-specific config file named {@code nomulus-config-ENVIRONMENT.yaml} is also loaded
* and those values merged into the POJO.
*/
static RegistryConfigSettings getConfigSettings() {
String configFilePath =
String.format(
ENVIRONMENT_CONFIG_FORMAT, Ascii.toLowerCase(RegistryEnvironment.get().name()));
String customYaml = readResourceUtf8(RegistryConfig.class, configFilePath);
return YamlUtils.getConfigSettings(YAML_CONFIG_PROD, customYaml, RegistryConfigSettings.class);
}
/** Dagger module for providing configuration settings. */
@Module
public static final class ConfigModule {
@ -1514,7 +1537,7 @@ public final class RegistryConfig {
*/
@VisibleForTesting
public static final Supplier<RegistryConfigSettings> CONFIG_SETTINGS =
memoize(YamlUtils::getConfigSettings);
memoize(RegistryConfig::getConfigSettings);
private static String formatComments(String text) {
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()

View file

@ -1,140 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.config;
import static com.google.common.base.Ascii.toLowerCase;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.flogger.FluentLogger;
import java.util.Map;
import java.util.Optional;
import org.yaml.snakeyaml.Yaml;
/**
* Utility methods for dealing with YAML.
*
* <p>There are always two YAML configuration files that are used: the {@code default-config.yaml}
* file, which contains default configuration for all environments, and the environment-specific
* {@code nomulus-config-ENVIRONMENT.yaml} file, which contains overrides for the default values for
* environment-specific settings such as the App Engine project ID. The environment-specific
* configuration can be blank, but it must exist.
*/
public final class YamlUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String ENVIRONMENT_CONFIG_FORMAT = "files/nomulus-config-%s.yaml";
private static final String YAML_CONFIG_PROD =
readResourceUtf8(RegistryConfig.class, "files/default-config.yaml");
/**
* Loads the POJO of type {@code T} from merged YAML configuration files.
*
* @param defaultYaml content of the default YAML file.
* @param customYaml content of the custom YAML file, to override default values.
* @param clazz type of the POJO loaded from the merged YAML files.
* @throws IllegalStateException if the configuration files don't exist or are invalid
*/
public static <T> T getConfigSettings(String defaultYaml, String customYaml, Class<T> clazz) {
try {
String mergedYaml = mergeYaml(defaultYaml, customYaml);
return new Yaml().loadAs(mergedYaml, clazz);
} catch (Exception e) {
throw new IllegalStateException(
"Fatal error: Environment configuration YAML file is invalid", e);
}
}
/**
* Loads the {@link RegistryConfigSettings} POJO from the YAML configuration files.
*
* <p>The {@code default-config.yaml} file in this directory is loaded first, and a fatal error is
* thrown if it cannot be found or if there is an error parsing it. Separately, the
* environment-specific config file named {@code nomulus-config-ENVIRONMENT.yaml} is also loaded
* and those values merged into the POJO.
*/
static RegistryConfigSettings getConfigSettings() {
String configFilePath =
String.format(ENVIRONMENT_CONFIG_FORMAT, toLowerCase(RegistryEnvironment.get().name()));
String customYaml = readResourceUtf8(RegistryConfig.class, configFilePath);
return getConfigSettings(YAML_CONFIG_PROD, customYaml, RegistryConfigSettings.class);
}
/**
* Recursively merges two YAML documents together.
*
* <p>Any fields that are specified in customYaml will override fields of the same path in
* defaultYaml. Additional fields in customYaml that aren't specified in defaultYaml will be
* ignored. The schemas of all fields that are present must be identical, e.g. it is an error to
* override a field that has a Map value in the default YAML with a field of any other type in the
* custom YAML.
*
* <p>Only maps are handled recursively; lists are simply overridden in place as-is, as are maps
* whose name is suffixed with "Map" -- this allows entire maps to be overridden, rather than
* merged.
*/
static String mergeYaml(String defaultYaml, String customYaml) {
Yaml yaml = new Yaml();
Map<String, Object> yamlMap = loadAsMap(yaml, defaultYaml).get();
Optional<Map<String, Object>> customMap = loadAsMap(yaml, customYaml);
if (customMap.isPresent()) {
yamlMap = mergeMaps(yamlMap, customMap.get());
logger.atFine().log("Successfully loaded environment configuration YAML file.");
} else {
logger.atWarning().log("Ignoring empty environment configuration YAML file.");
}
return yaml.dump(yamlMap);
}
/**
* Recursively merges a custom map into a default map, and returns the merged result.
*
* <p>All keys in the default map that are also specified in the custom map are overridden with
* the custom map's value. This runs recursively on all contained maps.
*/
@SuppressWarnings("unchecked")
private static Map<String, Object> mergeMaps(
Map<String, Object> defaultMap, Map<String, Object> customMap) {
for (String key : defaultMap.keySet()) {
if (!customMap.containsKey(key)) {
continue;
}
Object newValue;
if (defaultMap.get(key) instanceof Map && !key.endsWith("Map")) {
newValue =
mergeMaps(
(Map<String, Object>) defaultMap.get(key),
(Map<String, Object>) customMap.get(key));
} else {
newValue = customMap.get(key);
}
defaultMap.put(key, newValue);
}
return defaultMap;
}
/**
* Returns a structured map loaded from a YAML config string.
*
* <p>If the YAML string is empty or does not contain any data (e.g. it's only comments), then
* absent is returned.
*/
@SuppressWarnings("unchecked")
private static Optional<Map<String, Object>> loadAsMap(Yaml yaml, String yamlString) {
return Optional.ofNullable((Map<String, Object>) yaml.load(yamlString));
}
private YamlUtils() {}
}