ee.ria.xroad.common.SystemPropertiesLoader.java Source code

Java tutorial

Introduction

Here is the source code for ee.ria.xroad.common.SystemPropertiesLoader.java

Source

/**
 * The MIT License
 * Copyright (c) 2015 Estonian Information System Authority (RIA), Population Register Centre (VRK)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ee.ria.xroad.common;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalINIConfiguration;
import org.apache.commons.configuration.SubnodeConfiguration;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Comparator;

import static org.apache.commons.lang3.ArrayUtils.contains;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;

/**
 * <p>SystemPropertiesLoader reads INI files and creates a Java system property
 * from each entry in the INI file. Properties in the INI file are grouped
 * using INI sections -- for each section's parameters, a new property is
 * created as: [PREFIX].[SECTION].[PARAM], where the [PREFIX] is a prefix
 * for all properties loaded with this loader's instance, [SECTION] is the
 * name of the section and [PARAM] is the name of the param.</p>
 *
 * <p>For example, the following INI file loaded with prefix <pre>com.example.</pre>
 * <pre>
 * [section1]
 * foo-bar = value1
 * foo-baz = value2
 *
 * [section2]
 * bar-baz = value3
 * </pre>
 * produces the following system properties:
 * <pre>
 * com.example.section1.foo-bar = value1
 * com.example.section1.foo-baz = value2
 * com.example.section2.bar-baz = value3
 * </pre>
 *
 * <p>
 * The SystemPropertiesLoader should be called as soon as possible when the
 * main class is created. For example:
 * <pre>
 * public class FooMain {
 *
 *     static {
 *         SystemPropertiesLoader.create().withCommonAndLocal()
 *             .with("my-ini-file1.ini")
 *             .with("my-ini-file2.ini")
 *             .load();
 *     }
 *
 *     public static void main(String args[]) throws Exception {
 *         // ...
 *     }
 *
 * }
 * </pre>
 * </p>
 */
@Slf4j
public class SystemPropertiesLoader {

    private static final String DEFAULT_PREFIX = SystemProperties.PREFIX;
    private static final String ADDON_GLOB = "*.ini";
    private static final String OVERRIDE_GLOB = "override-*.ini";
    static final Comparator<Path> LOADING_ORDER_COMPARATOR = Comparator.comparing(Path::getFileName);

    @Getter
    static final class FileWithSections {
        private final String name;
        private final String[] sections;

        private FileWithSections(String name, String... sections) {
            this.name = name;
            this.sections = sections;
        }
    }

    private final Map<String, String> loadedProperties = new HashMap<String, String>() {
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();

            List<String> sortedKeys = new ArrayList<>(keySet());
            Collections.sort(sortedKeys);

            for (String key : sortedKeys) {
                sb.append("\t");
                sb.append(key);
                sb.append(" = ");
                sb.append(get(key));
                sb.append(System.lineSeparator());
            }

            return sb.toString();
        }
    };

    private final String prefix;
    private final List<FileWithSections> files = new ArrayList<>();
    private final List<FileWithSections> optionalLocalFiles = new ArrayList<>();

    private boolean withCommon;
    private boolean withLocal;
    private boolean withAddOn;
    private boolean withOverrides = true;

    // ------------------------------------------------------------------------

    /**
     * Creates a new instance using the default prefix.
     * @return new loader instance
     */
    public static SystemPropertiesLoader create() {
        return create(DEFAULT_PREFIX);
    }

    /**
     * Creates a new instance using the specified prefix.
     * @param prefix the prefix
     * @return new loader instance
     */
    public static SystemPropertiesLoader create(String prefix) {
        return new SystemPropertiesLoader(prefix);
    }

    /**
     * Specifies that the system common INI file should be included when
     * loading.
     * @return this instance
     */
    public SystemPropertiesLoader withCommon() {
        withCommon = true;
        return this;
    }

    /**
     * Specifies that the system override INI files should not be included when
     * loading.
     * @return this instance
     */
    public SystemPropertiesLoader withoutOverrides() {
        withOverrides = false;
        return this;
    }

    /**
     * Specifies that the system local INI file should be included when
     * loading.
     * @return this instance
     */
    public SystemPropertiesLoader withLocal() {
        withLocal = true;
        return this;
    }

    /**
     * Specifies that the addon INI files should be included when
     * loading.
     * @return this instance
     */
    public SystemPropertiesLoader withAddOn() {
        withAddOn = true;
        return this;
    }

    /**
     * Specifies that the system common and local INI files should be included
     * when loading.
     * @return this instance
     */
    public SystemPropertiesLoader withCommonAndLocal() {
        return withCommon().withLocal();
    }

    /**
     * Specifies the ini file to be loaded.
     * @param fileName the file name of the INI.
     * @param sectionNames optional section names to be parsed from the INI.
     * If not specified, all sections are parsed.
     * @return this instance
     */
    public SystemPropertiesLoader with(String fileName, String... sectionNames) {
        files.add(new FileWithSections(fileName, sectionNames));
        return this;
    }

    /**
     * Specifies the optional local ini file to be loaded.
     * @param fileName the file name of the INI.
     * @param sectionNames optional section names to be parsed from the INI.
     * If not specified, all sections are parsed.
     * @return this instance
     */
    public SystemPropertiesLoader withLocalOptional(String fileName, String... sectionNames) {
        optionalLocalFiles.add(new FileWithSections(fileName, sectionNames));
        return this;
    }

    /**
     * Does the actual loading of the INI files. Glob-defined files are loaded in alphabetical
     * order based on the filename.
     */
    public void load() {
        if (withCommon) {
            load(new FileWithSections(SystemProperties.CONF_FILE_COMMON));
        }

        files.forEach(this::load);

        if (withAddOn) {
            try {
                Path addOnDir = Paths.get(SystemProperties.CONF_FILE_ADDON_PATH);
                loadFilesInOrder(addOnDir, ADDON_GLOB, LOADING_ORDER_COMPARATOR);
            } catch (IOException e) {
                log.error("Cannot load addon configuration", e);
            }
        }

        if (withOverrides) {
            try {
                Path overrideDir = Paths.get(SystemProperties.getConfPath(), "conf.d");
                loadFilesInOrder(overrideDir, OVERRIDE_GLOB, LOADING_ORDER_COMPARATOR);
            } catch (IOException e) {
                log.error("Cannot load override configuration", e);
            }
        }

        if (withLocal) {
            load(new FileWithSections(SystemProperties.CONF_FILE_USER_LOCAL));
        }

        optionalLocalFiles.forEach(f -> {
            if (Files.isReadable(Paths.get(f.getName()))) {
                load(f);
            }
        });

        log.debug("Loaded properties:\n{}", loadedProperties);
    }

    // ------------------------------------------------------------------------

    protected SystemPropertiesLoader(String prefix) {
        this.prefix = prefix;
    }

    static List<Path> getFilePaths(Path dir, String glob) throws IOException {
        try (DirectoryStream<Path> dStream = Files.newDirectoryStream(dir, glob)) {
            List<Path> filePaths = new ArrayList<>();
            dStream.forEach(filePaths::add);
            return filePaths;
        }
    }

    void loadFilesInOrder(Path dir, String glob, Comparator<Path> comp) throws IOException {
        getFilePaths(dir, glob).stream().sorted(comp).forEach(path -> load(new FileWithSections(path.toString())));
    }

    private void load(FileWithSections file) {
        try {
            // turn off list delimiting (before parsing),
            // otherwise we lose everything after first ","
            // in loadSection/sec.getString(key)
            HierarchicalINIConfiguration ini = new HierarchicalINIConfiguration();
            ini.setDelimiterParsingDisabled(true);
            ini.load(file.getName());

            for (String sectionName : ini.getSections()) {
                if (isEmpty(file.getSections()) || contains(file.getSections(), sectionName)) {
                    loadSection(sectionName, ini.getSection(sectionName));
                }
            }
        } catch (ConfigurationException e) {
            log.warn("Error while loading {}: {}", file.getName(), e);
        }
    }

    private void loadSection(String sectionName, SubnodeConfiguration sec) {
        sec.getKeys().forEachRemaining(key -> setProperty(prefix + sectionName + "." + key, sec.getString(key)));
    }

    protected void setProperty(String key, String value) {
        System.setProperty(key, value);

        loadedProperties.put(key, value);
    }
}