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

io.rxmicro.config.internal.model.ConfigProperties 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.internal.model;

import io.rxmicro.config.ConfigException;
import io.rxmicro.config.ConfigSource;
import io.rxmicro.logger.Logger;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static io.rxmicro.common.CommonConstants.EMPTY_STRING;
import static io.rxmicro.common.util.Formats.format;
import static io.rxmicro.config.Config.RX_MICRO_CONFIG_DIRECTORY_NAME;
import static io.rxmicro.config.Config.RX_MICRO_CONFIG_ENVIRONMENT_VARIABLE_PREFIX;
import static io.rxmicro.config.Config.RX_MICRO_CONFIG_FILE_NAME;
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.RXMICRO_FILE_AT_THE_CURRENT_DIR;
import static io.rxmicro.config.ConfigSource.RXMICRO_FILE_AT_THE_HOME_DIR;
import static io.rxmicro.config.ConfigSource.RXMICRO_FILE_AT_THE_RXMICRO_CONFIG_DIR;
import static io.rxmicro.config.ConfigSource.SEPARATE_CLASS_PATH_RESOURCE;
import static io.rxmicro.config.ConfigSource.SEPARATE_FILE_AT_THE_CURRENT_DIR;
import static io.rxmicro.config.ConfigSource.SEPARATE_FILE_AT_THE_HOME_DIR;
import static io.rxmicro.config.ConfigSource.SEPARATE_FILE_AT_THE_RXMICRO_CONFIG_DIR;
import static io.rxmicro.config.internal.ExternalSourceProviderFactory.getCurrentDir;
import static io.rxmicro.config.internal.ExternalSourceProviderFactory.getEnvironmentVariables;
import static io.rxmicro.config.internal.model.PropertyNames.USER_HOME_PROPERTY;
import static io.rxmicro.resource.PropertiesResources.loadProperties;
import static java.util.Locale.ENGLISH;
import static java.util.stream.Collectors.toList;

/**
 * @author nedis
 * @since 0.7
 */
public abstract class ConfigProperties {

    protected static final Map SYSTEM_ENV = getEnvironmentVariables();

    protected static final Properties SYSTEM_PROPERTIES = System.getProperties();

    protected static final String USER_HOME = SYSTEM_PROPERTIES.getProperty(USER_HOME_PROPERTY);

    protected static final String RX_MICRO_CONFIG_DIRECTORY = USER_HOME + "/" + RX_MICRO_CONFIG_DIRECTORY_NAME;

    protected static final String CURRENT_DIR = getCurrentDir();

    protected static final Map>> RESOURCE_CACHE = new WeakHashMap<>();

    protected static final String ENVIRONMENT_VARIABLE_PREFIX =
            Optional.ofNullable(SYSTEM_ENV.get(RX_MICRO_CONFIG_ENVIRONMENT_VARIABLE_PREFIX))
                    .map(v -> v.endsWith("_") ? v : v + '_')
                    .orElse(EMPTY_STRING);

    protected final String namespace;

    protected final String upperNamespace;

    public ConfigProperties(final String namespace) {
        this.namespace = namespace;
        this.upperNamespace = ENVIRONMENT_VARIABLE_PREFIX + namespace.toUpperCase(ENGLISH).replace('-', '_');
    }

    protected abstract Logger getLogger();

    public final void discoverProperties(final Set configSources,
                                         final Map commandLineArgs) {
        final DebugMessageBuilder debugMessageBuilder = new DebugMessageBuilder(
                namespace, getLogger().isDebugEnabled(), configSources, commandLineArgs
        );
        discoverProperties(configSources, commandLineArgs, debugMessageBuilder);
        getLogger().debug(debugMessageBuilder.toString());
    }

    private void discoverProperties(final Set configSources,
                                    final Map commandLineArgs,
                                    final DebugMessageBuilder debugMessageBuilder) {
        for (final ConfigSource configSource : configSources) {
            if (configSource == DEFAULT_CONFIG_VALUES) {
                loadDefaultConfigValues(debugMessageBuilder);
            } else if (configSource == SEPARATE_CLASS_PATH_RESOURCE) {
                loadFromClassPathResource(namespace, false, debugMessageBuilder);
            } else if (configSource == RXMICRO_CLASS_PATH_RESOURCE) {
                loadFromClassPathResource(RX_MICRO_CONFIG_FILE_NAME, true, debugMessageBuilder);
            } else if (configSource == ENVIRONMENT_VARIABLES) {
                loadFromEnvironmentVariables(debugMessageBuilder);
            } else if (configSource == RXMICRO_FILE_AT_THE_HOME_DIR) {
                loadFromPropertiesFileIfExists(USER_HOME, RX_MICRO_CONFIG_FILE_NAME, true, debugMessageBuilder);
            } else if (configSource == RXMICRO_FILE_AT_THE_RXMICRO_CONFIG_DIR) {
                loadFromPropertiesFileIfExists(RX_MICRO_CONFIG_DIRECTORY, RX_MICRO_CONFIG_FILE_NAME, true, debugMessageBuilder);
            } else if (configSource == RXMICRO_FILE_AT_THE_CURRENT_DIR) {
                loadFromPropertiesFileIfExists(CURRENT_DIR, RX_MICRO_CONFIG_FILE_NAME, true, debugMessageBuilder);
            } else if (configSource == SEPARATE_FILE_AT_THE_HOME_DIR) {
                loadFromPropertiesFileIfExists(USER_HOME, namespace, false, debugMessageBuilder);
            } else if (configSource == SEPARATE_FILE_AT_THE_RXMICRO_CONFIG_DIR) {
                loadFromPropertiesFileIfExists(RX_MICRO_CONFIG_DIRECTORY, namespace, false, debugMessageBuilder);
            } else if (configSource == SEPARATE_FILE_AT_THE_CURRENT_DIR) {
                loadFromPropertiesFileIfExists(CURRENT_DIR, namespace, false, debugMessageBuilder);
            } else if (configSource == JAVA_SYSTEM_PROPERTIES) {
                loadFromJavaSystemProperties(debugMessageBuilder);
            } else {
                throw new ConfigException("Unsupported load order: " + configSource);
            }
        }
        if (!commandLineArgs.isEmpty()) {
            loadFromCommandLineArguments(commandLineArgs, debugMessageBuilder);
        }
    }

    protected abstract void loadDefaultConfigValues(DebugMessageBuilder debugMessageBuilder);

    protected final void loadFromClassPathResource(final String name,
                                                   final boolean useFullName,
                                                   final DebugMessageBuilder debugMessageBuilder) {
        final String fullClassPathFileName = name + ".properties";
        final Supplier>> propertiesSupplier;
        if (useFullName) {
            propertiesSupplier = () -> RESOURCE_CACHE.computeIfAbsent(name, n -> loadProperties(fullClassPathFileName));
        } else {
            propertiesSupplier = () -> loadProperties(fullClassPathFileName);
        }
        loadResource(propertiesSupplier, "classpath resource", fullClassPathFileName, useFullName, debugMessageBuilder);
    }

    protected final void loadFromPropertiesFileIfExists(final String path,
                                                        final String fileName,
                                                        final boolean useFullName,
                                                        final DebugMessageBuilder debugMessageBuilder) {
        final Path fullFilePath = Paths.get(format("?/?.properties", path, fileName)).toAbsolutePath();
        final String fullFilePathName = fullFilePath.toString();
        final Supplier>> propertiesSupplier;
        if (useFullName) {
            propertiesSupplier = () -> RESOURCE_CACHE.computeIfAbsent(fullFilePathName, n -> loadProperties(fullFilePath));
        } else {
            propertiesSupplier = () -> loadProperties(fullFilePath);
        }
        loadResource(propertiesSupplier, "config file", fullFilePathName, useFullName, debugMessageBuilder);
    }

    protected abstract void loadResource(Supplier>> propertiesSupplier,
                                         String resourceType,
                                         String resourceName,
                                         boolean useFullName,
                                         DebugMessageBuilder debugMessageBuilder);

    protected abstract void loadFromJavaSystemProperties(DebugMessageBuilder debugMessageBuilder);

    protected final void loadFromCommandLineArguments(final Map commandLineArgs,
                                                      final DebugMessageBuilder debugMessageBuilder) {
        loadFromMap(commandLineArgs, "command line arguments", debugMessageBuilder, false);
    }

    protected final void loadFromEnvironmentVariables(final DebugMessageBuilder debugMessageBuilder) {
        loadFromMap(SYSTEM_ENV, "environment variables", debugMessageBuilder, true);
    }

    protected abstract void loadFromMap(Map map,
                                        String sourceName,
                                        DebugMessageBuilder debugMessageBuilder,
                                        boolean isEnvironmentVariable);

    public abstract void setProperties();

    /**
     * @author nedis
     * @since 0.7
     */
    protected static final class DebugMessageBuilder {

        private static final String SHIFT = "  ";

        private static final Object INSTANCE = null;

        private final boolean debugEnabled;

        private final String namespace;

        private final List messages;

        private final Map, Object> resolvedEntries;

        private final Set configSources;

        private final Map commandLineArgs;

        private int count;

        protected DebugMessageBuilder(final String namespace,
                                      final boolean debugEnabled,
                                      final Set configSources,
                                      final Map commandLineArgs) {
            this.configSources = configSources;
            this.commandLineArgs = commandLineArgs;
            this.debugEnabled = debugEnabled;
            this.namespace = namespace;
            this.messages = debugEnabled ? new ArrayList<>() : List.of();
            this.resolvedEntries = debugEnabled ? new LinkedHashMap<>() : Map.of();
        }

        public void append(final String message,
                           final Object arg1) {
            if (debugEnabled) {
                messages.add(SHIFT + SHIFT + format(message, arg1));
            }
        }

        public void append(final String message,
                           final Object arg1,
                           final Object arg2) {
            if (debugEnabled) {
                messages.add(SHIFT + SHIFT + format(message, arg1, arg2));
            }
        }

        public void append(final String message,
                           final Object arg1,
                           final Object arg2,
                           final Object arg3) {
            if (debugEnabled) {
                messages.add(SHIFT + SHIFT + format(message, arg1, arg2, arg3));
            }
        }

        public void addResolvedEntries(final Set> resolvedEntries) {
            if (debugEnabled) {
                count += resolvedEntries.size();
                resolvedEntries.forEach(e -> this.resolvedEntries.put(e, INSTANCE));
            }
        }

        @Override
        public String toString() {
            if (debugEnabled) {
                messages.add(0, format("Discovering properties for '?' namespace:", namespace));
                messages.add(1, format("?Config source: ?", SHIFT, new ConfigSourceProvider(configSources, commandLineArgs)));
                if (count == 0) {
                    messages.add(format("?No properties found for '?' namespace. Using default config instance!", SHIFT, namespace));
                } else {
                    messages.add(format("?Property(ies) discovered for '?' namespace:", SHIFT, namespace));
                    messages.add(format("??Count: ?, Entries: ?", SHIFT, SHIFT, count, resolvedEntries.keySet()));
                }
                return String.join(System.lineSeparator(), messages);
            } else {
                return "";
            }
        }
    }

    /**
     * @author nedis
     * @since 0.7
     */
    protected static final class ConfigSourceProvider {

        private final Set configSources;

        private final Map commandLineArgs;

        protected ConfigSourceProvider(final Set configSources,
                                       final Map commandLineArgs) {
            this.configSources = configSources;
            this.commandLineArgs = commandLineArgs;
        }

        @Override
        public String toString() {
            if (commandLineArgs.isEmpty()) {
                return configSources.toString();
            } else {
                return Stream.concat(
                        configSources.stream().map(Objects::toString),
                        Stream.of("COMMAND_LINE_ARGUMENTS")
                ).collect(toList()).toString();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy