001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.settings; 018 019import static org.apache.juneau.commons.function.Suppliers.*; 020import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022 023import java.nio.charset.*; 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.function.*; 027 028import org.apache.juneau.commons.function.*; 029import org.apache.juneau.commons.reflect.*; 030 031/** 032 * Encapsulates Java system properties with support for global and per-thread overrides for unit testing. 033 * 034 * <p> 035 * This class provides a thread-safe way to access system properties that can be overridden at both the global level 036 * and per-thread level, making it useful for unit tests that need to temporarily change system property 037 * values without affecting other tests or threads. 038 * 039 * <p> 040 * Settings instances are created using the {@link Builder} pattern. Use {@link #create()} to create a new builder, 041 * or {@link #get()} to get the singleton instance (which is created using default builder settings). 042 * 043 * <h2 class='section'>Lookup Order:</h2> 044 * <p> 045 * When retrieving a property value, the lookup order is: 046 * <ol> 047 * <li>Per-thread store (if set via {@link #setLocal(String, String)}) 048 * <li>Global store (if set via {@link #setGlobal(String, String)}) 049 * <li>Sources added at runtime through {@link #addSource(PropertySource)}, newest first 050 * <li>The system-default <c>Config</c>, when juneau-config is on the classpath (SPI order 100) 051 * <li>Command-line args (SPI order 50) 052 * <li>Java system properties (SPI order 40) 053 * <li>Environment variables, verbatim (SPI order 30) 054 * <li>The <c>.env</c> file (SPI order 20) 055 * <li>The jar manifest (SPI order 10) 056 * <li>Environment variables, relaxed spelling (registered first, so checked last) 057 * </ol> 058 * 059 * <p> 060 * If SPI registered no system-property source (a shaded jar that does not merge <c>META-INF/services</c>, for example), 061 * a fallback system-property source sits just above the relaxed environment source. 062 * 063 * <h2 class='section'>Sources vs Stores:</h2> 064 * <ul class='spaced-list'> 065 * <li><b>Sources</b> ({@link PropertySource}) - Provide read-only access to property values. Examples: {@link FunctionalPropertySource} 066 * <li><b>Stores</b> ({@link PropertyStore}) - Provide read/write access to property values. Examples: {@link MapStore}, {@link FunctionalPropertyStore} 067 * <li>Stores can be used as sources (they extend {@link PropertySource}), so you can add stores via {@link Builder#addSource(PropertySource)} 068 * </ul> 069 * 070 * <h2 class='section'>Features:</h2> 071 * <ul class='spaced-list'> 072 * <li>System property access - read Java system properties with type conversion via {@link StringSetting} 073 * <li>Global overrides - override system properties globally for all threads (stored in a {@link PropertyStore}) 074 * <li>Per-thread overrides - override system properties for specific threads (stored in a per-thread {@link PropertyStore}) 075 * <li>Custom sources - add arbitrary property sources (e.g., Spring properties, environment variables, config files) via the {@link Builder} 076 * <li>Disable override support - system property to prevent new global overrides from being set 077 * <li>Type-safe accessors - type conversion methods on {@link StringSetting} for common types: Integer, Long, Boolean, Double, Float, File, Path, URI, Charset 078 * <li>Resettable suppliers - settings are returned as {@link StringSetting} instances that can be reset to force recomputation 079 * </ul> 080 * 081 * <h2 class='section'>Usage Examples:</h2> 082 * <p class='bjava'> 083 * <jc>// Get a system property as a StringSetting (using singleton instance)</jc> 084 * StringSetting <jv>setting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.property"</js>); 085 * String <jv>value</jv> = <jv>setting</jv>.get(); <jc>// Get the string value</jc> 086 * 087 * <jc>// Get with type conversion using StringSetting methods</jc> 088 * Setting<Integer> <jv>intSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.int.property"</js>).asInteger(); 089 * Setting<Long> <jv>longSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.long.property"</js>).asLong(); 090 * Setting<Boolean> <jv>boolSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.bool.property"</js>).asBoolean(); 091 * Setting<Double> <jv>doubleSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.double.property"</js>).asDouble(); 092 * Setting<Float> <jv>floatSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.float.property"</js>).asFloat(); 093 * Setting<File> <jv>fileSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.file.property"</js>).asFile(); 094 * Setting<Path> <jv>pathSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.path.property"</js>).asPath(); 095 * Setting<URI> <jv>uriSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.uri.property"</js>).asURI(); 096 * Setting<Charset> <jv>charsetSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.charset.property"</js>).asCharset(); 097 * 098 * <jc>// Use custom type conversion</jc> 099 * Setting<MyCustomType> <jv>customSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.custom.property"</js>).asType(MyCustomType.<jk>class</jk>); 100 * 101 * <jc>// Reset a setting to force recomputation</jc> 102 * <jv>setting</jv>.reset(); 103 * 104 * <jc>// Override for current thread (useful in unit tests)</jc> 105 * Settings.<jsf>get</jsf>().setLocal(<js>"my.property"</js>, <js>"test-value"</js>); 106 * <jc>// ... test code that uses the override ...</jc> 107 * Settings.<jsf>get</jsf>().unsetLocal(<js>"my.property"</js>); <jc>// Remove specific override</jc> 108 * <jc>// OR</jc> 109 * Settings.<jsf>get</jsf>().clearLocal(); <jc>// Clear all thread-local overrides</jc> 110 * 111 * <jc>// Set global override (applies to all threads)</jc> 112 * Settings.<jsf>get</jsf>().setGlobal(<js>"my.property"</js>, <js>"global-value"</js>); 113 * <jc>// ... code that uses the global override ...</jc> 114 * Settings.<jsf>get</jsf>().unsetGlobal(<js>"my.property"</js>); <jc>// Remove specific override</jc> 115 * <jc>// OR</jc> 116 * Settings.<jsf>get</jsf>().clearGlobal(); <jc>// Clear all global overrides</jc> 117 * 118 * <jc>// Create a custom Settings instance with custom sources (e.g., Spring properties)</jc> 119 * MapStore <jv>springSource</jv> = <jk>new</jk> MapStore(); 120 * <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, <js>"jdbc:postgresql://localhost/db"</js>); 121 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 122 * .addSource(<jv>springSource</jv>) 123 * .addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty)) 124 * .build(); 125 * </p> 126 * 127 * <h2 class='section'>System Properties:</h2> 128 * <ul class='spaced-list'> 129 * <li><c>juneau.settings.disableGlobal</c> (system property) or <c>JUNEAU_SETTINGS_DISABLEGLOBAL</c> (system env) - If set to <c>true</c>, prevents new global overrides 130 * from being set via {@link #setGlobal(String, String)}. Existing global overrides will still be 131 * returned by {@link #get(String)} until explicitly removed. 132 * <p class='bnote'> 133 * Note: This property is read once at class initialization time when creating the singleton instance 134 * and cannot be changed at runtime. Changing the system property after the class has been loaded will 135 * have no effect on the singleton instance. However, you can create custom Settings instances using 136 * {@link #create()} that ignore this property. 137 * </p> 138 * </ul> 139 */ 140@SuppressWarnings({ 141 "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_globalDisabled) 142 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 143 "unchecked" // Type erasure requires unchecked cast 144}) 145public class Settings { 146 147 /** 148 * System property source that delegates to {@link System#getProperty(String)}. 149 */ 150 public static final PropertySource SYSTEM_PROPERTY_SOURCE = FunctionalPropertySource.of(System::getProperty); 151 152 private static final Set<String> FROM_STRING_METHOD_NAMES = new LinkedHashSet<>(Arrays.asList("fromString", "parse", "forName", "valueOf")); 153 154 /** 155 * System environment variable source that delegates to {@link System#getenv(String)}. 156 */ 157 public static final PropertySource SYSTEM_ENV_SOURCE = FunctionalPropertySource.of(System::getenv); 158 159 /** 160 * {@link #SYSTEM_ENV_SOURCE} wrapped with {@link RelaxedPropertySource relaxed binding}, so a single logical key 161 * resolves from any of the common environment-variable spellings (e.g. {@code my.prop} / {@code myProp} / 162 * {@code MySection/myKey} all satisfied by {@code MY_PROP} / {@code MY_SECTION_MY_KEY}). 163 * 164 * <p> 165 * This is the env source used by the default {@link #get() singleton}. It is verbatim-first, so exact-match 166 * behavior is unchanged — a relaxed variant is only probed when the exact name misses. 167 * 168 * <p> 169 * Its runtime class is a {@link CachingPropertySource} wrapping the relaxed source. The environment is immutable 170 * inside the JVM, so caching is safe, and every chain that uses this constant shares one cache. 171 * 172 * @since 10.0.0 173 */ 174 public static final PropertySource RELAXED_SYSTEM_ENV_SOURCE = CachingPropertySource.of(new RelaxedPropertySource(SYSTEM_ENV_SOURCE)); 175 176 private static final String DISABLE_GLOBAL_PROP = "juneau.settings.disableGlobal"; 177 private static final String MSG_globalDisabled = "Global settings not enabled"; 178 private static final String MSG_localDisabled = "Local settings not enabled"; 179 180 /** 181 * Returns properties for this Settings object itself. 182 * Note that these are initialized at startup and not changeable through System.setProperty(). 183 */ 184 private static final Optional<String> initProperty(String property) { 185 var v = SYSTEM_PROPERTY_SOURCE.get(property); 186 if (v.isPresent()) 187 return v.value(); // Not testable 188 v = SYSTEM_ENV_SOURCE.get(ucr(property.replace('.', '_'))); 189 if (v.isPresent()) 190 return v.value(); // Not testable 191 return oe(); 192 } 193 194 /** 195 * Creates a new builder for constructing a Settings instance. 196 * 197 * <p> 198 * This method provides a convenient way to create custom Settings instances with specific 199 * configuration. The builder allows you to configure stores, sources, and other settings 200 * before building the final Settings instance. 201 * 202 * <h4 class='section'>Example:</h4> 203 * <p class='bjava'> 204 * <jc>// Create a custom Settings instance</jc> 205 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 206 * .globalStore(() -> <jk>new</jk> MapStore()) 207 * .localStore(() -> <jk>new</jk> MapStore()) 208 * .addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty)) 209 * .build(); 210 * </p> 211 * 212 * @return A new Builder instance. 213 */ 214 public static Builder create() { 215 return new Builder(); 216 } 217 218 /** 219 * Builder for creating Settings instances. 220 */ 221 public static class Builder { 222 private Supplier<PropertyStore> globalStoreSupplier = MapStore::new; 223 private Supplier<PropertyStore> localStoreSupplier = MapStore::new; 224 private final List<PropertySource> sources = new ArrayList<>(); 225 private final Map<Class<?>,Function<String,?>> customTypeFunctions = new IdentityHashMap<>(); 226 227 /** 228 * Sets the supplier for the global store. 229 * 230 * @param supplier The supplier for the global store. Must not be <c>null</c>. Can supply null to disable global store. 231 * @return This builder for method chaining. 232 */ 233 public Builder globalStore(NullableSupplier<PropertyStore> supplier) { 234 this.globalStoreSupplier = reqnn("supplier", supplier); 235 return this; 236 } 237 238 /** 239 * Sets the supplier for the local (per-thread) store. 240 * 241 * @param supplier The supplier for the local store. Must not be <c>null</c>. 242 * @return This builder for method chaining. 243 */ 244 public Builder localStore(NullableSupplier<PropertyStore> supplier) { 245 this.localStoreSupplier = reqnn("supplier", supplier); 246 return this; 247 } 248 249 /** 250 * Sets the sources list, replacing any existing sources. 251 * 252 * @param sources The sources to set. Must not be <c>null</c> or contain <c>null</c> elements. 253 * @return This builder for method chaining. 254 */ 255 @SafeVarargs 256 public final Builder setSources(PropertySource...sources) { 257 reqnns("sources", sources); 258 this.sources.clear(); 259 for (var source : sources) { 260 this.sources.add(source); 261 } 262 return this; 263 } 264 265 /** 266 * Adds a source to the sources list. 267 * 268 * @param source The source to add. Must not be <c>null</c>. 269 * @return This builder for method chaining. 270 */ 271 public Builder addSource(PropertySource source) { 272 reqnn("source", source); 273 this.sources.add(source); 274 return this; 275 } 276 277 /** 278 * Adds a functional source to the sources list. 279 * 280 * @param source The functional source to add. Must not be <c>null</c>. 281 * @return This builder for method chaining. 282 */ 283 public Builder addSource(FunctionalPropertySource source) { 284 return addSource((PropertySource)source); 285 } 286 287 /** 288 * Discovers and registers {@link PropertySourceProvider} instances via {@link ServiceLoader}. 289 * 290 * <p> 291 * Providers are sorted by {@link PropertySourceProvider#order()} ascending. 292 * 293 * @return This builder for method chaining. 294 */ 295 public Builder useServiceLoader() { 296 return addProviders(ServiceLoader.load(PropertySourceProvider.class).stream().map(ServiceLoader.Provider::get).toList()); 297 } 298 299 /** Sorts the providers by {@link PropertySourceProvider#order()} ascending, creates their sources and appends the non-null ones. */ 300 Builder addProviders(Collection<? extends PropertySourceProvider> providers) { 301 providers.stream() 302 .sorted(Comparator.comparingInt(PropertySourceProvider::order)) 303 .map(PropertySourceProvider::create) 304 .filter(Objects::nonNull) 305 .forEach(this::addSource); 306 return this; 307 } 308 309 /** Inserts a source at the given index. */ 310 Builder insertSource(int index, PropertySource source) { 311 reqnn("source", source); 312 this.sources.add(index, source); 313 return this; 314 } 315 316 /** An unmodifiable view of the sources registered so far. */ 317 List<PropertySource> sourceList() { 318 return Collections.unmodifiableList(sources); 319 } 320 321 /** 322 * Registers a custom type conversion function for the specified type. 323 * 324 * <p> 325 * This allows you to add support for converting string values to custom types when using 326 * {@link Settings#get(String, Object)}. The function will be used to convert string values 327 * to the specified type. 328 * 329 * <h4 class='section'>Example:</h4> 330 * <p class='bjava'> 331 * <jc>// Register a custom converter for a custom type</jc> 332 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 333 * .addTypeFunction(Integer.<jk>class</jk>, Integer::valueOf) 334 * .addTypeFunction(MyCustomType.<jk>class</jk>, MyCustomType::fromString) 335 * .build(); 336 * 337 * <jc>// Now you can use get() with these types</jc> 338 * Integer <jv>intValue</jv> = <jv>custom</jv>.get(<js>"my.int.property"</js>, 0); 339 * MyCustomType <jv>customValue</jv> = <jv>custom</jv>.get(<js>"my.custom.property"</js>, MyCustomType.DEFAULT); 340 * </p> 341 * 342 * @param <T> The type to register a converter for. 343 * @param type The class type to register a converter for. Must not be <c>null</c>. 344 * @param function The function that converts a string to the specified type. Must not be <c>null</c>. 345 * @return This builder for method chaining. 346 */ 347 public <T> Builder addTypeFunction(Class<T> type, Function<String,T> function) { 348 reqnn("type", type); 349 reqnn("function", function); 350 customTypeFunctions.put(type, function); 351 return this; 352 } 353 354 /** 355 * Builds a Settings instance from this builder. 356 * 357 * @return A new Settings instance. 358 */ 359 public Settings build() { 360 return new Settings(this); 361 } 362 } 363 364 /** 365 * Builds the default chain: the relaxed env source, then the SPI providers sorted by order, then (only when no SPI 366 * provider registered a system-property source) {@link #SYSTEM_PROPERTY_SOURCE} at index 1. 367 * 368 * <p> 369 * Detection may fail safe (keep a duplicate) but must never fail unsafe (drop the only system-property source). 370 */ 371 static Builder defaultBuilder(Collection<PropertySourceProvider> providers) { 372 var b = new Builder().setSources(RELAXED_SYSTEM_ENV_SOURCE).addProviders(providers); 373 if (b.sourceList().stream().noneMatch(Settings::isSpiSystemPropertySource)) 374 b.insertSource(1, SYSTEM_PROPERTY_SOURCE); // no SPI sysprop source: keep today's fallback at today's index 375 return b; 376 } 377 378 @SuppressWarnings({ 379 "java:S1872" // Exact-class match by name on purpose: any classloader counts, subclasses must not (fail safe). 380 }) 381 private static boolean isSpiSystemPropertySource(PropertySource s) { 382 return s.getClass().getName().equals(SystemPropertyPropertySource.class.getName()); // exact class name, any loader 383 } 384 385 private static final Settings INSTANCE = defaultBuilder(ServiceLoader.load(PropertySourceProvider.class).stream().map(ServiceLoader.Provider::get).toList()) 386 .globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false) ? () -> null : MapStore::new) 387 .build(); 388 389 /** 390 * Returns the singleton instance of Settings. 391 * 392 * @return The singleton Settings instance. 393 */ 394 public static Settings get() { 395 return INSTANCE; 396 } 397 398 private final Memoizer<PropertyStore> globalStore; 399 @SuppressWarnings({ 400 "java:S5164" // Cleanup method provided: cleanup() 401 }) 402 private final ThreadLocal<PropertyStore> localStore; 403 private final List<PropertySource> sources; 404 private final Map<Class<?>,Function<String,?>> toTypeFunctions; 405 406 /** 407 * Constructor. 408 */ 409 private Settings(Builder builder) { 410 this.globalStore = memoizer(builder.globalStoreSupplier); 411 this.localStore = ThreadLocal.withInitial(builder.localStoreSupplier); 412 this.sources = new CopyOnWriteArrayList<>(builder.sources); 413 this.toTypeFunctions = new ConcurrentHashMap<>(builder.customTypeFunctions); 414 } 415 416 /** 417 * Adds a property source after this settings instance has been built. 418 * 419 * @param source The property source to add. Must not be <jk>null</jk>. 420 * @return This object for method chaining. 421 */ 422 public Settings addSource(PropertySource source) { 423 reqnn("source", source); 424 sources.add(source); 425 return this; 426 } 427 428 /** 429 * Returns the number of {@link PropertySource}s currently registered with this 430 * {@code Settings} instance. 431 * 432 * <p> 433 * Intended primarily for tests that need to verify nothing has leaked extra sources into the 434 * process-wide {@link #get() Settings.get()} singleton — the historical failure mode 435 * documented where a per-{@code RestContext} 436 * config bridge added a {@code ConfigPropertySource} per cached {@code RestContext} via 437 * {@link #addSource(PropertySource)} and never removed it. 438 * 439 * @return The current source count. Always {@code >= 0}. 440 */ 441 public int sourceCount() { 442 return sources.size(); 443 } 444 445 /** 446 * Returns a live, read-only view of the registered sources, in registration order. Package-private, for tests. 447 * 448 * @return An unmodifiable view; later {@link #addSource} / {@link #removeSource} calls show through. 449 */ 450 List<PropertySource> sources() { 451 return Collections.unmodifiableList(sources); 452 } 453 454 /** 455 * Removes a previously-added property source. 456 * 457 * <p> 458 * The match is by identity ({@code ==}), not equality. Callers that mutate the singleton 459 * {@link #get() Settings.get()} from a test should capture the exact reference returned to/passed 460 * into {@link #addSource(PropertySource)} and pass it back here in their teardown to avoid 461 * cross-test bleed. 462 * 463 * @param source The property source to remove. Must not be <jk>null</jk>. 464 * @return <jk>true</jk> if the source was present and removed, <jk>false</jk> otherwise. 465 */ 466 public boolean removeSource(PropertySource source) { 467 reqnn("source", source); 468 // Identity-based removal so that PropertySource impls without a sensible equals() still 469 // round-trip cleanly through add/remove pairs. 470 // Removals are serialized so the index found in the snapshot is still valid when removed; addSource only appends. 471 synchronized (sources) { 472 var snapshot = sources.toArray(new PropertySource[0]); 473 for (var i = 0; i < snapshot.length; i++) { 474 if (snapshot[i] == source) { 475 sources.remove(i); 476 return true; 477 } 478 } 479 } 480 return false; 481 } 482 483 /** 484 * Returns a {@link StringSetting} for the specified system property. 485 * 486 * <p> 487 * The returned {@link StringSetting} is a resettable supplier that caches the lookup result. 488 * Use the {@link StringSetting#asInteger()}, {@link StringSetting#asBoolean()}, etc. methods 489 * to convert to different types. 490 * 491 * <p> 492 * The lookup order is: 493 * <ol> 494 * <li>Per-thread override (if set via {@link #setLocal(String, String)}) 495 * <li>Global override (if set via {@link #setGlobal(String, String)}) 496 * <li>Sources added at runtime through {@link #addSource(PropertySource)}, newest first 497 * <li>The system-default <c>Config</c>, when juneau-config is on the classpath (SPI order 100) 498 * <li>Command-line args (SPI order 50) 499 * <li>Java system properties (SPI order 40) 500 * <li>Environment variables, verbatim (SPI order 30) 501 * <li>The <c>.env</c> file (SPI order 20) 502 * <li>The jar manifest (SPI order 10) 503 * <li>Environment variables, relaxed spelling (registered first, so checked last) 504 * </ol> 505 * 506 * <p> 507 * If SPI registered no system-property source (a shaded jar that does not merge <c>META-INF/services</c>, for example), 508 * a fallback system-property source sits just above the relaxed environment source. 509 * 510 * @param name The property name. Must not be <jk>null</jk>. 511 * @return A {@link StringSetting} that provides the resolved property value. 512 */ 513 public StringSetting get(String name) { 514 reqnn("name", name); 515 return new StringSetting(this, () -> resolve(name)); 516 } 517 518 /** 519 * Walks the thread-local store, the global store, then every source newest first. The one copy of the lookup order. 520 * 521 * @return The resolved value, or <jk>null</jk> when no store or source defines the name or the winner is an explicit null. 522 */ 523 private String resolve(String name) { 524 // 1. Check thread-local override 525 var v = localStore.get().get(name); 526 if (v.isPresent()) 527 return v.value().orElse(null); // Present result: Optional.empty() means explicit null override. 528 529 // 2. Check global override 530 v = globalLookup(name); 531 if (v.isPresent()) 532 return v.value().orElse(null); // Present result: Optional.empty() means explicit null override. 533 534 // 3. Check sources in reverse order (last added first) 535 // Iterate one snapshot so a concurrent removeSource() can't shrink the list between size() and get(i). 536 var snapshot = sources.toArray(new PropertySource[0]); 537 for (int i = snapshot.length - 1; i >= 0; i--) { 538 var source = snapshot[i]; 539 var result = source.get(name); 540 if (result.isPresent()) 541 return result.value().orElse(null); 542 } 543 544 return null; 545 } 546 547 /** 548 * Returns the override value for the specified property, consulting only the per-thread 549 * (local) and global stores — <i>not</i> the sources list. 550 * 551 * <p> 552 * Intended for callers (notably the {@code @Value} resolution path) that interleave a 553 * caller-scoped property source between the Settings override stores and the sources list. 554 * Using this method, the caller can honor the "{@link #setLocal(String, String) local} 555 * / {@link #setGlobal(String, String) global} override wins" contract before consulting 556 * its own scoped sources, and still fall through to {@link #get(String)} (which checks 557 * the sources list) afterwards without the override stores accidentally being consulted twice. 558 * 559 * <p> 560 * Lookup order honored by this method: 561 * <ol> 562 * <li>Per-thread (local) store 563 * <li>Global store 564 * </ol> 565 * 566 * <p> 567 * If a store is present with an empty value (i.e. an explicit null override), the returned 568 * {@link Optional} is {@code Optional.empty()} but {@link #isOverridden(String)} would 569 * return {@code true}. Callers needing to distinguish "absent" from "explicit null" should 570 * use {@link #isOverridden(String)} alongside this method. 571 * 572 * @param name The property name. Must not be <jk>null</jk>. 573 * @return The override value, or {@link Optional#empty()} if neither store carries an override. 574 * @see #isOverridden(String) 575 * @see #get(String) 576 */ 577 public Optional<String> getOverride(String name) { 578 reqnn("name", name); 579 var v = localStore.get().get(name); 580 if (v.isPresent()) 581 return v.value(); 582 v = globalLookup(name); 583 if (v.isPresent()) 584 return v.value(); 585 return oe(); 586 } 587 588 /** 589 * Returns <jk>true</jk> if the specified property has an override in either the per-thread 590 * (local) or global store. 591 * 592 * <p> 593 * Distinguishes the "explicit null override" case — where {@link #getOverride(String)} 594 * returns {@link Optional#empty()} because the override value itself was {@code null} — 595 * from the "no override" case. 596 * 597 * @param name The property name. Must not be <jk>null</jk>. 598 * @return <jk>true</jk> if either store carries an override (even with a {@code null} value). 599 */ 600 public boolean isOverridden(String name) { 601 reqnn("name", name); 602 return localStore.get().get(name).isPresent() || globalLookup(name).isPresent(); 603 } 604 605 /** Looks up an override in the global store, which is absent (null) when global overrides are disabled. */ 606 private PropertyLookupResult globalLookup(String name) { 607 var store = globalStore.get(); 608 return n(store) ? PropertyLookupResult.missing() : store.get(name); 609 } 610 611 /** 612 * Looks up a system property, returning a default value if not found. 613 * 614 * <p> 615 * This method searches for a value using the same lookup order as {@link #get(String)}. 616 * If a value is found, it is converted to the type of the default value using {@link #toType(String, Class)}. 617 * Supported types include any type that has a static method with signature <c>public static <T> T anyName(String arg)</c> 618 * or a public constructor with signature <c>public T(String arg)</c>, such as {@link Boolean}, {@link Integer}, {@link Charset}, {@link java.io.File}, etc. 619 * 620 * <h4 class='section'>Example:</h4> 621 * <p class='bjava'> 622 * <jc>// System property: -Dmy.property=true</jc> 623 * Boolean <jv>flag</jv> = get(<js>"my.property"</js>, <jk>false</jk>); <jc>// true</jc> 624 * 625 * <jc>// Environment variable: MY_PROPERTY=UTF-8</jc> 626 * Charset <jv>charset</jv> = get(<js>"my.property"</js>, Charset.defaultCharset()); <jc>// UTF-8</jc> 627 * 628 * <jc>// Not found, returns default</jc> 629 * String <jv>value</jv> = get(<js>"nonexistent"</js>, <js>"default"</js>); <jc>// "default"</jc> 630 * </p> 631 * 632 * @param <T> The type to convert the value to. 633 * @param name The property name. Must not be <jk>null</jk>. 634 * @param def The default value to return if not found. Must not be <jk>null</jk>. 635 * @return The found value (converted to type T), or the default value if not found. 636 * @see #get(String) 637 * @see #toType(String, Class) 638 */ 639 public <T> T get(String name, T def) { 640 reqnn("def", def); 641 reqnn("name", name); 642 var v = resolve(name); 643 if (v == null) 644 return def; 645 var t = toType(v, (Class<T>)def.getClass()); 646 return t == null ? def : t; // matches the filter(Objects::nonNull) in StringSetting.asType 647 } 648 649 /** 650 * Sets a global override for the specified property. 651 * 652 * <p> 653 * This override will apply to all threads and takes precedence over system properties. 654 * However, per-thread overrides (set via {@link #setLocal(String, String)}) will still take precedence 655 * over global overrides. 656 * 657 * <p> 658 * If the <c>juneau.settings.disableGlobal</c> system property is set to <c>true</c>, 659 * this method will throw an exception. This allows system administrators 660 * to prevent applications from overriding system properties globally. 661 * 662 * <p> 663 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the system 664 * property to return empty. Use {@link #unsetGlobal(String)} to completely remove the override. 665 * 666 * @param name The property name. Must not be <jk>null</jk>. 667 * @param value The override value, or <c>null</c> to set an empty override. 668 * @return This object for method chaining. 669 * @see #unsetGlobal(String) 670 * @see #clearGlobal() 671 */ 672 public Settings setGlobal(String name, String value) { 673 reqnn("name", name); 674 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).set(name, value); 675 return this; 676 } 677 678 /** 679 * Removes a global override for the specified property. 680 * 681 * <p> 682 * After calling this method, the property will fall back to the system property value 683 * (or per-thread override if one exists). 684 * 685 * @param name The property name. Must not be <jk>null</jk>. 686 * @see #setGlobal(String, String) 687 * @see #clearGlobal() 688 */ 689 public void unsetGlobal(String name) { 690 reqnn("name", name); 691 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).unset(name); 692 } 693 694 /** 695 * Sets a per-thread override for the specified property. 696 * 697 * <p> 698 * This override will only apply to the current thread and takes precedence over global overrides 699 * and system properties. This is particularly useful in unit tests where you need to temporarily 700 * change a system property value without affecting other threads or tests. 701 * 702 * <p> 703 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the property 704 * to return empty for this thread. Use {@link #unsetLocal(String)} to completely remove the override. 705 * 706 * @param name The property name. Must not be <jk>null</jk>. 707 * @param value The override value, or <c>null</c> to set an empty override. 708 * @return This object for method chaining. 709 * @see #unsetLocal(String) 710 * @see #clearLocal() 711 */ 712 public Settings setLocal(String name, String value) { 713 reqnn("name", name); 714 chk(nn(localStore.get()), MSG_localDisabled); 715 localStore.get().set(name, value); 716 return this; 717 } 718 719 /** 720 * Removes a per-thread override for the specified property. 721 * 722 * <p> 723 * After calling this method, the property will fall back to the global override (if set) 724 * or the system property value for the current thread. 725 * 726 * @param name The property name. Must not be <jk>null</jk>. 727 * @see #setLocal(String, String) 728 * @see #clearLocal() 729 */ 730 public void unsetLocal(String name) { 731 reqnn("name", name); 732 chk(nn(localStore.get()), MSG_localDisabled); 733 localStore.get().unset(name); 734 } 735 736 /** 737 * Clears all per-thread overrides for the current thread. 738 * 739 * <p> 740 * After calling this method, all properties will fall back to global overrides (if set) 741 * or system property values for the current thread. 742 * 743 * <p> 744 * This is typically called in a <c>@AfterEach</c> or <c>@After</c> test method to clean up 745 * thread-local overrides after a test completes. 746 * 747 * @return This object for method chaining. 748 * @see #setLocal(String, String) 749 * @see #unsetLocal(String) 750 */ 751 public Settings clearLocal() { 752 chk(nn(localStore.get()), MSG_localDisabled); 753 localStore.get().clear(); 754 return this; 755 } 756 757 /** 758 * Cleans up thread-local storage for the current thread. 759 * 760 * <p> 761 * This method should be called when a thread is finished using this Settings instance to prevent memory leaks 762 * in thread pool environments where threads are reused. 763 */ 764 public void cleanup() { 765 if (localStore != null) { 766 localStore.remove(); 767 } 768 } 769 770 /** 771 * Clears all global overrides. 772 * 773 * <p> 774 * After calling this method, all properties will fall back to resolver values 775 * (or per-thread overrides if they exist). 776 * 777 * @return This object for method chaining. 778 * @see #setGlobal(String, String) 779 * @see #unsetGlobal(String) 780 */ 781 public Settings clearGlobal() { 782 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).clear(); 783 return this; 784 } 785 786 /** 787 * Converts a string to the specified type using reflection to find conversion methods or constructors. 788 * 789 * <p> 790 * This method attempts to convert a string to the specified type using the following lookup order: 791 * <ol> 792 * <li>Custom type functions registered via {@link Builder#addTypeFunction(Class, Function)}</li> 793 * <li>Special handling for {@link String} (returns the string as-is)</li> 794 * <li>Special handling for {@link Enum} types (uses {@link Enum#valueOf(Class, String)})</li> 795 * <li>Reflection lookup for static methods with signature <c>public static <T> T anyName(String arg)</c> 796 * on the target class</li> 797 * <li>Reflection lookup for static methods on the superclass (for abstract classes like {@link Charset} 798 * where concrete implementations need to use the abstract class's static method)</li> 799 * <li>Reflection lookup for public constructors with signature <c>public T(String arg)</c></li> 800 * </ol> 801 * 802 * <p> 803 * When a conversion method or constructor is found via reflection, it is cached in the 804 * <c>toTypeFunctions</c> map for future use. 805 * 806 * <h4 class='section'>Examples:</h4> 807 * <ul class='spaced-list'> 808 * <li><c>Boolean</c> - Uses <c>Boolean.valueOf(String)</c> static method 809 * <li><c>Integer</c> - Uses <c>Integer.valueOf(String)</c> static method 810 * <li><c>Charset</c> - Uses <c>Charset.forName(String)</c> static method (even for concrete implementations) 811 * <li><c>File</c> - Uses <c>File(String)</c> constructor 812 * </ul> 813 * 814 * @param <T> The target type. 815 * @param s The string to convert. Must not be <jk>null</jk>. 816 * @param c The target class. Must not be <jk>null</jk>. 817 * @return The converted value. 818 * @throws RuntimeException If the type is not supported for conversion (no static method or constructor found). 819 */ 820 @SuppressWarnings({ 821 "rawtypes" // Raw types necessary for generic type handling 822 }) 823 public <T> T toType(String s, Class<T> c) { 824 reqnn("s", s); 825 reqnn("c", c); 826 var f = (Function<String,T>)toTypeFunctions.get(c); 827 if (f == null) { 828 if (c == String.class) 829 return (T)s; 830 if (c.isEnum()) 831 return (T)Enum.valueOf((Class<? extends Enum>)c, s); 832 ClassInfoTyped<T> ci = info(c); 833 f = ci.getDeclaredMethod(x -> x.isStatic() && x.hasParameterTypes(String.class) && x.hasReturnType(c) && FROM_STRING_METHOD_NAMES.contains(x.getName())).map(x -> (Function<String,T>)s2 -> x.invoke(null, s2)).orElse(null); 834 // Also accept `static T parse(CharSequence)` etc. — common for java.time types (Instant, 835 // LocalDate, Duration) whose static factories declare the wider CharSequence parameter. 836 if (f == null) 837 f = ci.getDeclaredMethod(x -> x.isStatic() && x.hasParameterTypes(CharSequence.class) && x.hasReturnType(c) && FROM_STRING_METHOD_NAMES.contains(x.getName())).map(x -> (Function<String,T>)s2 -> x.invoke(null, s2)).orElse(null); 838 if (f == null) 839 f = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).map(x -> (Function<String,T>)x::newInstance).orElse(null); 840 if (f != null) 841 toTypeFunctions.putIfAbsent(c, f); 842 } 843 if (f == null) 844 throw rex("Invalid env type: %s", c); 845 return f.apply(s); 846 } 847} 848