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(newEnvVariable("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
548    public static ConfigOrigin newEnvVariable(String description) {
549        return SimpleConfigOrigin.newEnvVariable(description);
550    }
551}