All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rxmicro.config.Configs Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2020. https://rxmicro.io
 *
 * 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 io.rxmicro.config;

import io.rxmicro.config.internal.EnvironmentConfigLoader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static io.rxmicro.common.util.Formats.format;
import static io.rxmicro.config.Config.getDefaultNameSpace;
import static io.rxmicro.config.ConfigSource.DEFAULT_CONFIG_VALUES;
import static io.rxmicro.config.ConfigSource.ENVIRONMENT_VARIABLES;
import static io.rxmicro.config.ConfigSource.JAVA_SYSTEM_PROPERTIES;
import static io.rxmicro.config.ConfigSource.RXMICRO_CLASS_PATH_RESOURCE;
import static io.rxmicro.config.ConfigSource.SEPARATE_CLASS_PATH_RESOURCE;
import static io.rxmicro.config.ConfigSource.SEPARATE_FILE_AT_THE_RXMICRO_CONFIG_DIR;
import static io.rxmicro.config.internal.waitfor.component.WaitForUtils.withoutWaitForArguments;
import static java.util.Arrays.asList;
import static java.util.Map.entry;
import static java.util.stream.Collectors.toMap;

/**
 * Allows working with all supported configs.
 *
 * 

* To get an instance of the requested config it is necessary to use * {@link Configs#getConfig(Class)} or {@link Configs#getConfig(String, Class)} methods. * * @author nedis * @see Config * @see ConfigSource * @since 0.1 */ public final class Configs { private static final Set DEFAULT_CONFIG_LOAD_SOURCE_ORDER = new LinkedHashSet<>(asList( DEFAULT_CONFIG_VALUES, RXMICRO_CLASS_PATH_RESOURCE, SEPARATE_CLASS_PATH_RESOURCE, ENVIRONMENT_VARIABLES, JAVA_SYSTEM_PROPERTIES )); private static Configs instance; private final EnvironmentConfigLoader loader; private final Map storage; private final Map commandLineArgs; /** * Returns the requested config instance that corresponds to the provided {@code namespace} and {@code configClass}. * * @param namespace the requested namespace * @param configClass the requested config class * @param config type * @return the config instance * @throws ConfigException if Configs are not built * @throws IllegalArgumentException if the requested config class marked as singleton config class and * the requested namespace is not default namespace */ public static T getConfig(final String namespace, final Class configClass) { return getConfig(namespace, getDefaultNameSpace(configClass).equals(namespace), configClass); } /** * Returns the requested config instance that corresponds to the provided {@code configClass} and the default namespace. * *

* The RxMicro framework uses the {@link Config#getDefaultNameSpace(Class)} method to define the default namespace. * * @param configClass the requested config class * @param the config type * @return the config instance * @throws ConfigException if Configs are not built */ public static T getConfig(final Class configClass) { return getConfig(getDefaultNameSpace(configClass), true, configClass); } @SuppressWarnings("unchecked") private static T getConfig(final String namespace, final boolean defaultNamespace, final Class configClass) { if (instance == null) { throw new ConfigException( "Configs are not built. Use Configs.Builder to build configuration"); } if (!defaultNamespace && configClass.isAnnotationPresent(SingletonConfigClass.class)) { throw new IllegalArgumentException(format( "'?' config class defined as singleton! " + "It means that this config class can be used only with '?' namespace, but actual namespace is '?'!" + "Use default namespace or remove '@?' annotation!", configClass.getName(), getDefaultNameSpace(configClass), namespace, SingletonConfigClass.class.getSimpleName() )); } return (T) instance.storage.computeIfAbsent(namespace, n -> { final Config config = instance.loader.getEnvironmentConfig(namespace, configClass, instance.commandLineArgs); config.validate(namespace); return config; }); } private Map commandLineArgsToMap(final List commandLineArgs) { return commandLineArgs.isEmpty() ? Map.of() : commandLineArgs.stream().map(cmd -> { final String[] data = cmd.split("="); if (data.length != 2) { throw new ConfigException("Invalid command line arguments. " + "Expected: 'name_space.propertyName=propertyValue', but actual is '?'", cmd); } else { return entry(data[0], data[1]); } }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } private Configs(final Map storage, final Set configSources, final List commandLineArgs) { this.loader = new EnvironmentConfigLoader(configSources); this.storage = new ConcurrentHashMap<>(storage); this.commandLineArgs = commandLineArgsToMap(commandLineArgs); } /** * Allows configuring the {@link Configs} manager before usage. * * @author nedis * @since 0.1 */ @SuppressWarnings("UnusedReturnValue") public static final class Builder { private final List commandLineArgs = new ArrayList<>(); private final Map storage; private final Set configSources = new LinkedHashSet<>(DEFAULT_CONFIG_LOAD_SOURCE_ORDER); /** * Creates default {@link Builder} instance. */ public Builder() { storage = new HashMap<>(); } /** * Allows adding the configuration using java classes with custom namespace. * * @param namespace the custom namespace * @param config the created by developer config instance * @return the reference to this {@link Builder} instance * @throws ConfigException if the provided namespace is already configured */ public Builder withConfig(final String namespace, final Config config) { final Config oldConfig = storage.put(namespace, config); if (oldConfig != null) { throw new ConfigException( "'?' namespace is already configured. Old class is '?', new class is '?'", namespace, oldConfig.getClass().getName(), config.getClass().getName()); } return this; } /** * Allows adding configurations using java classes with default namespace. * *

* The RxMicro framework uses {@link Config#getDefaultNameSpace(Class)} method to define a default namespace. * * @param configs the var args of config instances * @return the reference to this {@link Builder} instance */ public Builder withConfigs(final Config... configs) { for (final Config config : configs) { withConfig(config.getNameSpace(), config); } return this; } /** * Allows adding the configuration using java classes with custom namespace. * * @param configs the map that contains entries with custom namespaces and config instances. * @return the reference to this {@link Builder} instance */ public Builder withConfigs(final Map configs) { configs.forEach(this::withConfig); return this; } /** * Allows changing the order of the configuration reading. * * @param sources the custom order of the configuration reading * @return the reference to this {@link Builder} instance */ public Builder withOrderedConfigSources(final ConfigSource... sources) { configSources.clear(); configSources.addAll(asList(sources)); return this; } /** * Disables all configuration sources. * *

* This method can be useful for tests. * * @return the reference to this {@link Builder} instance */ public Builder withoutAnyConfigSources() { configSources.clear(); return this; } /** * Enables all supported config sources according to natural order. * *

* Natural order is defined by {@link ConfigSource} enum. * * @return the reference to this {@link Builder} instance */ public Builder withAllConfigSources() { withOrderedConfigSources(ConfigSource.values()); return this; } /** * Enables config sources according to recommended order for docker or kubernetes environments. * *

* Recommended order for docker or kubernetes environments: *

    *
  1. Hardcoded config using annotations.
  2. *
  3. Config from file: {@code ~/.rxmicro/${name_space}.properties}
  4. *
  5. Config from env variables.
  6. *
* * @return the reference to this {@link Builder} instance */ public Builder withContainerConfigSources() { withOrderedConfigSources( DEFAULT_CONFIG_VALUES, SEPARATE_FILE_AT_THE_RXMICRO_CONFIG_DIR, ENVIRONMENT_VARIABLES ); return this; } /** * Enables the config reading from command line arguments. * *

* This type of configuration has the highest priority and overrides all other types. * *

* (Except of configuration using Java classes.) * * @param args command line arguments * @return the reference to this {@link Builder} instance */ public Builder withCommandLineArguments(final String... args) { if (args.length > 0) { commandLineArgs.addAll(withoutWaitForArguments(args)); } return this; } /** * Creates a new immutable instance of the {@link Configs} manager. * *

* Each subsequent invocation of this method overrides all configuration manager settings. * *

* (In any microservice project there is only one configuration manager object!) * *

* It means that if the developer creates several {@link Builder} instances, * it will be the last invocation of the build method that matters, the others will be ignored. */ public void build() { instance = new Configs(storage, configSources, commandLineArgs); } /** * Creates a new immutable instance of the {@link Configs} manager only if it not be created before. */ public void buildIfNotConfigured() { if (instance == null) { build(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy