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}