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

org.graylog2.featureflag.ImmutableFeatureFlagsCollector Maven / Gradle / Ivy

There is a newer version: 6.0.1
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.featureflag;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;
import static org.graylog2.bootstrap.CmdLineTool.GRAYLOG_ENVIRONMENT_VAR_PREFIX;
import static org.graylog2.bootstrap.CmdLineTool.GRAYLOG_SYSTEM_PROP_PREFIX;
import static org.graylog2.featureflag.FeatureFlagStringUtil.startsWithIgnoreCase;
import static org.graylog2.featureflag.FeatureFlagStringUtil.stringFormat;
import static org.graylog2.featureflag.FeatureFlagStringUtil.toUpperCase;

class ImmutableFeatureFlagsCollector {

    private static final Logger LOG = LoggerFactory.getLogger(ImmutableFeatureFlagsCollector.class);

    private static final String GRAYLOG_FF_ENVIRONMENT_VAR_PREFIX = GRAYLOG_ENVIRONMENT_VAR_PREFIX + "FEATURE_";
    private static final String GRAYLOG_FF_SYSTEM_PROP_PREFIX = GRAYLOG_SYSTEM_PROP_PREFIX + "feature.";

    private Map existingFlags = new HashMap<>();
    private final FeatureFlagsResources resources;
    private final String defaultPropertiesFile;
    private final String customPropertiesFile;

    public ImmutableFeatureFlagsCollector(FeatureFlagsResources resources, String defaultPropertiesFile, String customPropertiesFile) {
        this.resources = resources;
        this.defaultPropertiesFile = defaultPropertiesFile;
        this.customPropertiesFile = customPropertiesFile;
    }

    public Map toMap() {
        existingFlags = new HashMap<>();
        Map defaultPropertiesFlags = getDefaultPropertiesFlags(defaultPropertiesFile);
        addFlags(defaultPropertiesFlags, "default properties file");
        addCustomPropertiesFlags(customPropertiesFile);
        addSystemPropertiesFlags();
        addEnvironmentVariableFlags();
        logUsedFeatureFlags();
        logWarningForNoDefaultFlags(defaultPropertiesFlags.keySet());
        return existingFlags.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().value));
    }

    private void logWarningForNoDefaultFlags(Collection defaultFlags) {
        Map>> flagsWithoutDefault = existingFlags.entrySet().stream()
                .filter(e -> !defaultFlags.contains(e.getKey()))
                .collect(Collectors.groupingBy(e -> e.getValue().resourceType));
        if (!flagsWithoutDefault.isEmpty()) {
            LOG.warn("Following feature flags have no entry in the default config file: {}", flagsWithoutDefault);
        }
    }

    private void logUsedFeatureFlags() {
        LOG.info("Following feature flags are used: {}", existingFlags.entrySet().stream()
                .collect(Collectors.groupingBy(e -> e.getValue().resourceType)));
    }

    private Map getDefaultPropertiesFlags(String file) {
        try {
            return resources.defaultProperties(file);
        } catch (IOException e) {
            throw new RuntimeException(
                    stringFormat("Unable to read default feature flags file %s!", file), e);
        }
    }

    private void addCustomPropertiesFlags(String file) {
        try {
            addFlags(resources.customProperties(file), "custom properties file");
        } catch (IOException e) {
            LOG.debug("Unable to read custom feature flags file {}! Skipping...", file);
        }
    }

    private void addSystemPropertiesFlags() {
        addFlagsWithPrefix(GRAYLOG_FF_SYSTEM_PROP_PREFIX, resources.systemProperties(), "system properties");
    }

    private void addEnvironmentVariableFlags() {
        addFlagsWithPrefix(GRAYLOG_FF_ENVIRONMENT_VAR_PREFIX, resources.environmentVariables(), "environment variables");
    }

    private void addFlagsWithPrefix(String prefix, Map newFlags, String resourceType) {
        addFlags(newFlags, resourceType, s -> startsWithIgnoreCase(s, prefix), s -> s.substring(prefix.length()));
    }

    private void addFlags(Map newFlags, String resourceType) {
        addFlags(newFlags, resourceType, s -> true, Function.identity());
    }

    private void addFlags(Map newFlags,
                          String resourceType,
                          Predicate predicate,
                          Function transform) {
        Multimap possibleDuplicates = ArrayListMultimap.create();
        for (Map.Entry entry : newFlags.entrySet()) {
            if (predicate.test(entry.getKey())) {
                String key = transform.apply(entry.getKey());
                addFlag(key, entry.getValue(), resourceType);
                possibleDuplicates.put(toUpperCase(key), key);
            }
        }
        checkForDuplicates(possibleDuplicates, resourceType);
    }

    private void checkForDuplicates(Multimap possibleDuplicates, String source) {
        List> duplicates = possibleDuplicates.asMap().values().stream()
                .filter(collection -> collection.size() > 1)
                .collect(toList());
        if (!duplicates.isEmpty()) {
            throw new IllegalStateException(stringFormat("The following duplicate feature flags are found in %s: %s", source, duplicates));
        }
    }

    private void addFlag(String key, String value, String resourceType) {
        Optional existingFlag = existingFlags.keySet().stream()
                .filter(k -> k.equalsIgnoreCase(key))
                .findFirst();
        if (existingFlag.isPresent()) {
            add(existingFlag.get(), value, resourceType);
        } else {
            add(key, value, resourceType);
        }
    }

    private void add(String key, String value, String resourceType) {
        existingFlags.put(key, new FeatureFlagValue(value, resourceType));
    }

    private static class FeatureFlagValue {
        final String value;
        final String resourceType;

        private FeatureFlagValue(String value, String resourceType) {
            this.value = value;
            this.resourceType = resourceType;
        }

        @Override
        public String toString() {
            return value;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy