    001/**
    002 *   Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
    003 */
    004package com.typesafe.config.impl;
    005
    006import java.io.File;
    007import java.lang.ref.WeakReference;
    008import java.net.URL;
    009import java.time.Duration;
    010import java.util.ArrayList;
    011import java.util.Collections;
    012import java.util.HashMap;
    013import java.util.Iterator;
    014import java.util.List;
    015import java.util.Map;
    016import java.util.Properties;
    017import java.util.concurrent.Callable;
    018
    019import com.typesafe.config.Config;
    020import com.typesafe.config.ConfigException;
    021import com.typesafe.config.ConfigIncluder;
    022import com.typesafe.config.ConfigMemorySize;
    023import com.typesafe.config.ConfigObject;
    024import com.typesafe.config.ConfigOrigin;
    025import com.typesafe.config.ConfigParseOptions;
    026import com.typesafe.config.ConfigParseable;
    027import com.typesafe.config.ConfigValue;
    028import com.typesafe.config.impl.SimpleIncluder.NameSource;
    029
    030/**
    031 * Internal implementation detail, not ABI stable, do not touch.
    032 * For use only by the {@link com.typesafe.config} package.
    033 */
    034public class ConfigImpl {
    035    private static final String ENV_VAR_OVERRIDE_PREFIX = "CONFIG_FORCE_";
    036
    037    private static class LoaderCache {
    038        private Config currentSystemProperties;
    039        private WeakReference<ClassLoader> currentLoader;
    040        private Map<String, Config> cache;
    041
    042        LoaderCache() {
    043            this.currentSystemProperties = null;
    044            this.currentLoader = new WeakReference<ClassLoader>(null);
    045            this.cache = new HashMap<String, Config>();
    046        }
    047
    048        // for now, caching as long as the loader remains the same,
    049        // drop entire cache if it changes.
    050        synchronized Config getOrElseUpdate(ClassLoader loader, String key, Callable<Config> updater) {
    051            if (loader != currentLoader.get()) {
    052                // reset the cache if we start using a different loader
    053                cache.clear();
    054                currentLoader = new WeakReference<ClassLoader>(loader);
    055            }
    056
    057            Config systemProperties = systemPropertiesAsConfig();
    058            if (systemProperties != currentSystemProperties) {
    059                cache.clear();
    060                currentSystemProperties = systemProperties;
    061            }
    062
    063            Config config = cache.get(key);
    064            if (config == null) {
    065                try {
    066                    config = updater.call();
    067                } catch (RuntimeException e) {
    068                    throw e; // this will include ConfigException
    069                } catch (Exception e) {
    070                    throw new ConfigException.Generic(e.getMessage(), e);
    071                }
    072                if (config == null)
    073                    throw new ConfigException.BugOrBroken("null config from cache updater");
    074                cache.put(key, config);
    075            }
    076
    077            return config;
    078        }
    079    }
    080
    081    private static class LoaderCacheHolder {
    082        static final LoaderCache cache = new LoaderCache();
    083    }
    084
    085    public static Config computeCachedConfig(ClassLoader loader, String key,
    086            Callable<Config> updater) {
    087        LoaderCache cache;
    088        try {
    089            cache = LoaderCacheHolder.cache;
    090        } catch (ExceptionInInitializerError e) {
    091            throw ConfigImplUtil.extractInitializerError(e);
    092        }
    093        return cache.getOrElseUpdate(loader, key, updater);
    094    }
    095
    096
    097    static class FileNameSource implements SimpleIncluder.NameSource {
    098        @Override
    099        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    100            return Parseable.newFile(new File(name), parseOptions);
    101        }
    102    };
    103
    104    static class ClasspathNameSource implements SimpleIncluder.NameSource {
    105        @Override
    106        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    107            return Parseable.newResources(name, parseOptions);
    108        }
    109    };
    110
    111    static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource {
    112        final private Class<?> klass;
    113
    114        public ClasspathNameSourceWithClass(Class<?> klass) {
    115            this.klass = klass;
    116        }
    117
    118        @Override
    119        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    120            return Parseable.newResources(klass, name, parseOptions);
    121        }
    122    };
    123
    124    public static ConfigObject parseResourcesAnySyntax(Class<?> klass, String resourceBasename,
    125            ConfigParseOptions baseOptions) {
    126        NameSource source = new ClasspathNameSourceWithClass(klass);
    127        return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
    128    }
    129
    130    public static ConfigObject parseResourcesAnySyntax(String resourceBasename,
    131            ConfigParseOptions baseOptions) {
    132        NameSource source = new ClasspathNameSource();
    133        return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
    134    }
    135
    136    public static ConfigObject parseFileAnySyntax(File basename, ConfigParseOptions baseOptions) {
    137        NameSource source = new FileNameSource();
    138        return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions);
    139    }
    140
    141    static AbstractConfigObject emptyObject(String originDescription) {
    142        ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
    143                .newSimple(originDescription) : null;
    144        return emptyObject(origin);
    145    }
    146
    147    public static Config emptyConfig(String originDescription) {
    148        return emptyObject(originDescription).toConfig();
    149    }
    150
    151    static AbstractConfigObject empty(ConfigOrigin origin) {
    152        return emptyObject(origin);
    153    }
    154
    155    // default origin for values created with fromAnyRef and no origin specified
    156    final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
    157            .newSimple("hardcoded value");
    158    final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
    159            defaultValueOrigin, true);
    160    final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
    161            defaultValueOrigin, false);
    162    final private static ConfigNull defaultNullValue = new ConfigNull(
    163            defaultValueOrigin);
    164    final private static SimpleConfigList defaultEmptyList = new SimpleConfigList(
    165            defaultValueOrigin, Collections.<AbstractConfigValue> emptyList());
    166    final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject
    167            .empty(defaultValueOrigin);
    168
    169    private static SimpleConfigList emptyList(ConfigOrigin origin) {
    170        if (origin == null || origin == defaultValueOrigin)
    171            return defaultEmptyList;
    172        else
    173            return new SimpleConfigList(origin,
    174                    Collections.<AbstractConfigValue> emptyList());
    175    }
    176
    177    private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
    178        // we want null origin to go to SimpleConfigObject.empty() to get the
    179        // origin "empty config" rather than "hardcoded value"
    180        if (origin == defaultValueOrigin)
    181            return defaultEmptyObject;
    182        else
    183            return SimpleConfigObject.empty(origin);
    184    }
    185
    186    private static ConfigOrigin valueOrigin(String originDescription) {
    187        if (originDescription == null)
    188            return defaultValueOrigin;
    189        else
    190            return SimpleConfigOrigin.newSimple(originDescription);
    191    }
    192
    193    public static ConfigValue fromAnyRef(Object object, String originDescription) {
    194        ConfigOrigin origin = valueOrigin(originDescription);
    195        return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS);
    196    }
    197
    198    public static ConfigObject fromPathMap(
    199            Map<String, ? extends Object> pathMap, String originDescription) {
    200        ConfigOrigin origin = valueOrigin(originDescription);
    201        return (ConfigObject) fromAnyRef(pathMap, origin,
    202                FromMapMode.KEYS_ARE_PATHS);
    203    }
    204
    205    static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin,
    206            FromMapMode mapMode) {
    207        if (origin == null)
    208            throw new ConfigException.BugOrBroken(
    209                    "origin not supposed to be null");
    210
    211        if (object == null) {
    212            if (origin != defaultValueOrigin)
    213                return new ConfigNull(origin);
    214            else
    215                return defaultNullValue;
    216        } else if(object instanceof AbstractConfigValue) {
    217            return (AbstractConfigValue) object;
    218        } else if (object instanceof Boolean) {
    219            if (origin != defaultValueOrigin) {
    220                return new ConfigBoolean(origin, (Boolean) object);
    221            } else if ((Boolean) object) {
    222                return defaultTrueValue;
    223            } else {
    224                return defaultFalseValue;
    225            }
    226        } else if (object instanceof String) {
    227            return new ConfigString.Quoted(origin, (String) object);
    228        } else if (object instanceof Number) {
    229            // here we always keep the same type that was passed to us,
    230            // rather than figuring out if a Long would fit in an Int
    231            // or a Double has no fractional part. i.e. deliberately
    232            // not using ConfigNumber.newNumber() when we have a
    233            // Double, Integer, or Long.
    234            if (object instanceof Double) {
    235                return new ConfigDouble(origin, (Double) object, null);
    236            } else if (object instanceof Integer) {
    237                return new ConfigInt(origin, (Integer) object, null);
    238            } else if (object instanceof Long) {
    239                return new ConfigLong(origin, (Long) object, null);
    240            } else {
    241                return ConfigNumber.newNumber(origin,
    242                        ((Number) object).doubleValue(), null);
    243            }
    244        } else if (object instanceof Duration) {
    245            return new ConfigLong(origin, ((Duration) object).toMillis(), null);
    246        } else if (object instanceof Map) {
    247            if (((Map<?, ?>) object).isEmpty())
    248                return emptyObject(origin);
    249
    250            if (mapMode == FromMapMode.KEYS_ARE_KEYS) {
    251                Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
    252                for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
    253                    Object key = entry.getKey();
    254                    if (!(key instanceof String))
    255                        throw new ConfigException.BugOrBroken(
    256                                "bug in method caller: not valid to create ConfigObject from map with non-String key: "
    257                                        + key);
    258                    AbstractConfigValue value = fromAnyRef(entry.getValue(),
    259                            origin, mapMode);
    260                    values.put((String) key, value);
    261                }
    262
    263                return new SimpleConfigObject(origin, values);
    264            } else {
    265                return PropertiesParser.fromPathMap(origin, (Map<?, ?>) object);
    266            }
    267        } else if (object instanceof Iterable) {
    268            Iterator<?> i = ((Iterable<?>) object).iterator();
    269            if (!i.hasNext())
    270                return emptyList(origin);
    271
    272            List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
    273            while (i.hasNext()) {
    274                AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode);
    275                values.add(v);
    276            }
    277
    278            return new SimpleConfigList(origin, values);
    279        } else if (object instanceof ConfigMemorySize) {
    280            return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null);
    281        } else {
    282            throw new ConfigException.BugOrBroken(
    283                    "bug in method caller: not valid to create ConfigValue from: "
    284                            + object);
    285        }
    286    }
    287
    288    private static class DefaultIncluderHolder {
    289        static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
    290    }
    291
    292    static ConfigIncluder defaultIncluder() {
    293        try {
    294            return DefaultIncluderHolder.defaultIncluder;
    295        } catch (ExceptionInInitializerError e) {
    296            throw ConfigImplUtil.extractInitializerError(e);
    297        }
    298    }
    299
    300    private static Properties getSystemProperties() {
    301        // Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties
    302        final Properties systemProperties = System.getProperties();
    303        final Properties systemPropertiesCopy = new Properties();
    304        synchronized (systemProperties) {
    305            for (Map.Entry<Object, Object> entry: systemProperties.entrySet()) {
    306                // Java 11 introduces 'java.version.date', but we don't want that to
    307                // overwrite 'java.version'
    308                if (!entry.getKey().toString().startsWith("java.version.")) {
    309                    systemPropertiesCopy.put(entry.getKey(), entry.getValue());
    310                }
    311            }
    312        }
    313        return systemPropertiesCopy;
    314    }
    315
    316    private static AbstractConfigObject loadSystemProperties() {
    317        return (AbstractConfigObject) Parseable.newProperties(getSystemProperties(),
    318                ConfigParseOptions.defaults().setOriginDescription("system properties")).parse();
    319    }
    320
    321    private static class SystemPropertiesHolder {
    322        // this isn't final due to the reloadSystemPropertiesConfig() hack below
    323        static volatile AbstractConfigObject systemProperties = loadSystemProperties();
    324    }
    325
    326    static AbstractConfigObject systemPropertiesAsConfigObject() {
    327        try {
    328            return SystemPropertiesHolder.systemProperties;
    329        } catch (ExceptionInInitializerError e) {
    330            throw ConfigImplUtil.extractInitializerError(e);
    331        }
    332    }
    333
    334    public static Config systemPropertiesAsConfig() {
    335        return systemPropertiesAsConfigObject().toConfig();
    336    }
    337
    338    public static void reloadSystemPropertiesConfig() {
    339        // ConfigFactory.invalidateCaches() relies on this having the side
    340        // effect that it drops all caches
    341        SystemPropertiesHolder.systemProperties = loadSystemProperties();
    342    }
    343
    344    private static AbstractConfigObject loadEnvVariables() {
    345        return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv());
    346    }
    347
    348    private static class EnvVariablesHolder {
    349        static volatile AbstractConfigObject envVariables = loadEnvVariables();
    350    }
    351
    352    static AbstractConfigObject envVariablesAsConfigObject() {
    353        try {
    354            return EnvVariablesHolder.envVariables;
    355        } catch (ExceptionInInitializerError e) {
    356            throw ConfigImplUtil.extractInitializerError(e);
    357        }
    358    }
    359
    360    public static Config envVariablesAsConfig() {
    361        return envVariablesAsConfigObject().toConfig();
    362    }
    363
    364    public static void reloadEnvVariablesConfig() {
    365        // ConfigFactory.invalidateCaches() relies on this having the side
    366        // effect that it drops all caches
    367        EnvVariablesHolder.envVariables = loadEnvVariables();
    368    }
    369
    370
    371
    372    private static AbstractConfigObject loadEnvVariablesOverrides() {
    373        Map<String, String> env = new HashMap(System.getenv());
    374        Map<String, String> result = new HashMap();
    375
    376        for (String key : env.keySet()) {
    377            if (key.startsWith(ENV_VAR_OVERRIDE_PREFIX)) {
    378                result.put(ConfigImplUtil.envVariableAsProperty(key, ENV_VAR_OVERRIDE_PREFIX), env.get(key));
    379            }
    380        }
    381
    382        return PropertiesParser.fromStringMap(newSimpleOrigin("env variables overrides"), result);
    383    }
    384
    385    private static class EnvVariablesOverridesHolder {
    386        static volatile AbstractConfigObject envVariables = loadEnvVariablesOverrides();
    387    }
    388
    389    static AbstractConfigObject envVariablesOverridesAsConfigObject() {
    390        try {
    391            return EnvVariablesOverridesHolder.envVariables;
    392        } catch (ExceptionInInitializerError e) {
    393            throw ConfigImplUtil.extractInitializerError(e);
    394        }
    395    }
    396
    397    public static Config envVariablesOverridesAsConfig() {
    398        return envVariablesOverridesAsConfigObject().toConfig();
    399    }
    400
    401    public static void reloadEnvVariablesOverridesConfig() {
    402        // ConfigFactory.invalidateCaches() relies on this having the side
    403        // effect that it drops all caches
    404        EnvVariablesOverridesHolder.envVariables = loadEnvVariablesOverrides();
    405    }
    406
    407    public static Config defaultReference(final ClassLoader loader) {
    408        return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
    409            @Override
    410            public Config call() {
    411                Config unresolvedResources = unresolvedReference(loader);
    412                return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve();
    413            }
    414        });
    415    }
    416
    417    private static Config unresolvedReference(final ClassLoader loader) {
    418        return computeCachedConfig(loader, "unresolvedReference", new Callable<Config>() {
    419            @Override
    420            public Config call() {
    421                return Parseable.newResources("reference.conf",
    422                        ConfigParseOptions.defaults().setClassLoader(loader))
    423                    .parse().toConfig();
    424            }
    425        });
    426    }
    427
    428    /**
    429     * This returns the unresolved reference configuration, but before doing so,
    430     * it verifies that the reference configuration resolves, to ensure that it
    431     * is self contained and doesn't depend on any higher level configuration
    432     * files.
    433     */
    434    public static Config defaultReferenceUnresolved(final ClassLoader loader) {
    435        // First, verify that `reference.conf` resolves by itself.
    436        try {
    437            defaultReference(loader);
    438        } catch (ConfigException.UnresolvedSubstitution e) {
    439            throw e.addExtraDetail("Could not resolve substitution in reference.conf to a value: %s. All reference.conf files are required to be fully, independently resolvable, and should not require the presence of values for substitutions from further up the hierarchy.");
    440        }
    441        // Now load the unresolved version
    442        return unresolvedReference(loader);
    443    }
    444
    445
    446    private static class DebugHolder {
    447        private static String LOADS = "loads";
    448        private static String SUBSTITUTIONS = "substitutions";
    449
    450        private static Map<String, Boolean> loadDiagnostics() {
    451            Map<String, Boolean> result = new HashMap<String, Boolean>();
    452            result.put(LOADS, false);
    453            result.put(SUBSTITUTIONS, false);
    454
    455            // People do -Dconfig.trace=foo,bar to enable tracing of different things
    456            String s = System.getProperty("config.trace");
    457            if (s == null) {
    458                return result;
    459            } else {
    460                String[] keys = s.split(",");
    461                for (String k : keys) {
    462                    if (k.equals(LOADS)) {
    463                        result.put(LOADS, true);
    464                    } else if (k.equals(SUBSTITUTIONS)) {
    465                        result.put(SUBSTITUTIONS, true);
    466                    } else {
    467                        System.err.println("config.trace property contains unknown trace topic '"
    468                                + k + "'");
    469                    }
    470                }
    471                return result;
    472            }
    473        }
    474
    475        private static final Map<String, Boolean> diagnostics = loadDiagnostics();
    476
    477        private static final boolean traceLoadsEnabled = diagnostics.get(LOADS);
    478        private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS);
    479
    480        static boolean traceLoadsEnabled() {
    481            return traceLoadsEnabled;
    482        }
    483
    484        static boolean traceSubstitutionsEnabled() {
    485            return traceSubstitutionsEnabled;
    486        }
    487    }
    488
    489    public static boolean traceLoadsEnabled() {
    490        try {
    491            return DebugHolder.traceLoadsEnabled();
    492        } catch (ExceptionInInitializerError e) {
    493            throw ConfigImplUtil.extractInitializerError(e);
    494        }
    495    }
    496
    497    public static boolean traceSubstitutionsEnabled() {
    498        try {
    499            return DebugHolder.traceSubstitutionsEnabled();
    500        } catch (ExceptionInInitializerError e) {
    501            throw ConfigImplUtil.extractInitializerError(e);
    502        }
    503    }
    504
    505    public static void trace(String message) {
    506        System.err.println(message);
    507    }
    508
    509    public static void trace(int indentLevel, String message) {
    510        while (indentLevel > 0) {
    511            System.err.print("  ");
    512            indentLevel -= 1;
    513        }
    514        System.err.println(message);
    515    }
    516
    517    // the basic idea here is to add the "what" and have a canonical
    518    // toplevel error message. the "original" exception may however have extra
    519    // detail about what happened. call this if you have a better "what" than
    520    // further down on the stack.
    521    static ConfigException.NotResolved improveNotResolved(Path what,
    522            ConfigException.NotResolved original) {
    523        String newMessage = what.render()
    524                + " has not been resolved, you need to call Config#resolve(),"
    525                + " see API docs for Config#resolve()";
    526        if (newMessage.equals(original.getMessage()))
    527            return original;
    528        else
    529            return new ConfigException.NotResolved(newMessage, original);
    530    }
    531
    532    public static ConfigOrigin newSimpleOrigin(String description) {
    533        if (description == null) {
    534            return defaultValueOrigin;
    535        } else {
    536            return SimpleConfigOrigin.newSimple(description);
    537        }
    538    }
    539
    540    public static ConfigOrigin newFileOrigin(String filename) {
    541        return SimpleConfigOrigin.newFile(filename);
    542    }
    543
    544    public static ConfigOrigin newURLOrigin(URL url) {
    545        return SimpleConfigOrigin.newURL(url);
    546    }
    547}