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 * <h5 class='section'>Lookup Order:</h5> 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 * <h5 class='section'>Sources vs Stores:</h5> 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 * <h5 class='section'>Features:</h5> 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 * <h5 class='section'>Usage Examples:</h5> 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 * <h5 class='section'>System Properties:</h5> 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 * <h5 class='section'>Example:</h5> 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 * <h5 class='section'>Example:</h5> 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 private static boolean isSpiSystemPropertySource(PropertySource s) { 379 return s.getClass().getName().equals(SystemPropertyPropertySource.class.getName()); // exact class name, any loader 380 } 381 382 private static final Settings INSTANCE = defaultBuilder(ServiceLoader.load(PropertySourceProvider.class).stream().map(ServiceLoader.Provider::get).toList()) 383 .globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false) ? () -> null : MapStore::new) 384 .build(); 385 386 /** 387 * Returns the singleton instance of Settings. 388 * 389 * @return The singleton Settings instance. 390 */ 391 public static Settings get() { 392 return INSTANCE; 393 } 394 395 private final Memoizer<PropertyStore> globalStore; 396 @SuppressWarnings({ 397 "java:S5164" // Cleanup method provided: cleanup() 398 }) 399 private final ThreadLocal<PropertyStore> localStore; 400 private final List<PropertySource> sources; 401 private final Map<Class<?>,Function<String,?>> toTypeFunctions; 402 403 /** 404 * Constructor. 405 */ 406 private Settings(Builder builder) { 407 this.globalStore = memoizer(builder.globalStoreSupplier); 408 this.localStore = ThreadLocal.withInitial(builder.localStoreSupplier); 409 this.sources = new CopyOnWriteArrayList<>(builder.sources); 410 this.toTypeFunctions = new ConcurrentHashMap<>(builder.customTypeFunctions); 411 } 412 413 /** 414 * Adds a property source after this settings instance has been built. 415 * 416 * @param source The property source to add. Must not be <jk>null</jk>. 417 * @return This object for method chaining. 418 */ 419 public Settings addSource(PropertySource source) { 420 reqnn("source", source); 421 sources.add(source); 422 return this; 423 } 424 425 /** 426 * Returns the number of {@link PropertySource}s currently registered with this 427 * {@code Settings} instance. 428 * 429 * <p> 430 * Intended primarily for tests that need to verify nothing has leaked extra sources into the 431 * process-wide {@link #get() Settings.get()} singleton — the historical failure mode 432 * documented where a per-{@code RestContext} 433 * config bridge added a {@code ConfigPropertySource} per cached {@code RestContext} via 434 * {@link #addSource(PropertySource)} and never removed it. 435 * 436 * @return The current source count. Always {@code >= 0}. 437 */ 438 public int sourceCount() { 439 return sources.size(); 440 } 441 442 /** 443 * Returns a live, read-only view of the registered sources, in registration order. Package-private, for tests. 444 * 445 * @return An unmodifiable view; later {@link #addSource} / {@link #removeSource} calls show through. 446 */ 447 List<PropertySource> sources() { 448 return Collections.unmodifiableList(sources); 449 } 450 451 /** 452 * Removes a previously-added property source. 453 * 454 * <p> 455 * The match is by identity ({@code ==}), not equality. Callers that mutate the singleton 456 * {@link #get() Settings.get()} from a test should capture the exact reference returned to/passed 457 * into {@link #addSource(PropertySource)} and pass it back here in their teardown to avoid 458 * cross-test bleed. 459 * 460 * @param source The property source to remove. Must not be <jk>null</jk>. 461 * @return <jk>true</jk> if the source was present and removed, <jk>false</jk> otherwise. 462 */ 463 public boolean removeSource(PropertySource source) { 464 reqnn("source", source); 465 // Identity-based removal so that PropertySource impls without a sensible equals() still 466 // round-trip cleanly through add/remove pairs. 467 // Removals are serialized so the index found in the snapshot is still valid when removed; addSource only appends. 468 synchronized (sources) { 469 var snapshot = sources.toArray(new PropertySource[0]); 470 for (var i = 0; i < snapshot.length; i++) { 471 if (snapshot[i] == source) { 472 sources.remove(i); 473 return true; 474 } 475 } 476 } 477 return false; 478 } 479 480 /** 481 * Returns a {@link StringSetting} for the specified system property. 482 * 483 * <p> 484 * The returned {@link StringSetting} is a resettable supplier that caches the lookup result. 485 * Use the {@link StringSetting#asInteger()}, {@link StringSetting#asBoolean()}, etc. methods 486 * to convert to different types. 487 * 488 * <p> 489 * The lookup order is: 490 * <ol> 491 * <li>Per-thread override (if set via {@link #setLocal(String, String)}) 492 * <li>Global override (if set via {@link #setGlobal(String, String)}) 493 * <li>Sources added at runtime through {@link #addSource(PropertySource)}, newest first 494 * <li>The system-default <c>Config</c>, when juneau-config is on the classpath (SPI order 100) 495 * <li>Command-line args (SPI order 50) 496 * <li>Java system properties (SPI order 40) 497 * <li>Environment variables, verbatim (SPI order 30) 498 * <li>The <c>.env</c> file (SPI order 20) 499 * <li>The jar manifest (SPI order 10) 500 * <li>Environment variables, relaxed spelling (registered first, so checked last) 501 * </ol> 502 * 503 * <p> 504 * If SPI registered no system-property source (a shaded jar that does not merge <c>META-INF/services</c>, for example), 505 * a fallback system-property source sits just above the relaxed environment source. 506 * 507 * @param name The property name. Must not be <jk>null</jk>. 508 * @return A {@link StringSetting} that provides the resolved property value. 509 */ 510 public StringSetting get(String name) { 511 reqnn("name", name); 512 return new StringSetting(this, () -> resolve(name)); 513 } 514 515 /** 516 * Walks the thread-local store, the global store, then every source newest first. The one copy of the lookup order. 517 * 518 * @return The resolved value, or <jk>null</jk> when no store or source defines the name or the winner is an explicit null. 519 */ 520 private String resolve(String name) { 521 // 1. Check thread-local override 522 var v = localStore.get().get(name); 523 if (v.isPresent()) 524 return v.value().orElse(null); // Present result: Optional.empty() means explicit null override. 525 526 // 2. Check global override 527 v = globalLookup(name); 528 if (v.isPresent()) 529 return v.value().orElse(null); // Present result: Optional.empty() means explicit null override. 530 531 // 3. Check sources in reverse order (last added first) 532 // Iterate one snapshot so a concurrent removeSource() can't shrink the list between size() and get(i). 533 var snapshot = sources.toArray(new PropertySource[0]); 534 for (int i = snapshot.length - 1; i >= 0; i--) { 535 var source = snapshot[i]; 536 var result = source.get(name); 537 if (result.isPresent()) 538 return result.value().orElse(null); 539 } 540 541 return null; 542 } 543 544 /** 545 * Returns the override value for the specified property, consulting only the per-thread 546 * (local) and global stores — <i>not</i> the sources list. 547 * 548 * <p> 549 * Intended for callers (notably the {@code @Value} resolution path) that interleave a 550 * caller-scoped property source between the Settings override stores and the sources list. 551 * Using this method, the caller can honor the "{@link #setLocal(String, String) local} 552 * / {@link #setGlobal(String, String) global} override wins" contract before consulting 553 * its own scoped sources, and still fall through to {@link #get(String)} (which checks 554 * the sources list) afterwards without the override stores accidentally being consulted twice. 555 * 556 * <p> 557 * Lookup order honored by this method: 558 * <ol> 559 * <li>Per-thread (local) store 560 * <li>Global store 561 * </ol> 562 * 563 * <p> 564 * If a store is present with an empty value (i.e. an explicit null override), the returned 565 * {@link Optional} is {@code Optional.empty()} but {@link #isOverridden(String)} would 566 * return {@code true}. Callers needing to distinguish "absent" from "explicit null" should 567 * use {@link #isOverridden(String)} alongside this method. 568 * 569 * @param name The property name. Must not be <jk>null</jk>. 570 * @return The override value, or {@link Optional#empty()} if neither store carries an override. 571 * @see #isOverridden(String) 572 * @see #get(String) 573 */ 574 public Optional<String> getOverride(String name) { 575 reqnn("name", name); 576 var v = localStore.get().get(name); 577 if (v.isPresent()) 578 return v.value(); 579 v = globalLookup(name); 580 if (v.isPresent()) 581 return v.value(); 582 return oe(); 583 } 584 585 /** 586 * Returns <jk>true</jk> if the specified property has an override in either the per-thread 587 * (local) or global store. 588 * 589 * <p> 590 * Distinguishes the "explicit null override" case — where {@link #getOverride(String)} 591 * returns {@link Optional#empty()} because the override value itself was {@code null} — 592 * from the "no override" case. 593 * 594 * @param name The property name. Must not be <jk>null</jk>. 595 * @return <jk>true</jk> if either store carries an override (even with a {@code null} value). 596 */ 597 public boolean isOverridden(String name) { 598 reqnn("name", name); 599 return localStore.get().get(name).isPresent() || globalLookup(name).isPresent(); 600 } 601 602 /** Looks up an override in the global store, which is absent (null) when global overrides are disabled. */ 603 private PropertyLookupResult globalLookup(String name) { 604 var store = globalStore.get(); 605 return n(store) ? PropertyLookupResult.missing() : store.get(name); 606 } 607 608 /** 609 * Looks up a system property, returning a default value if not found. 610 * 611 * <p> 612 * This method searches for a value using the same lookup order as {@link #get(String)}. 613 * If a value is found, it is converted to the type of the default value using {@link #toType(String, Class)}. 614 * Supported types include any type that has a static method with signature <c>public static <T> T anyName(String arg)</c> 615 * 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. 616 * 617 * <h5 class='section'>Example:</h5> 618 * <p class='bjava'> 619 * <jc>// System property: -Dmy.property=true</jc> 620 * Boolean <jv>flag</jv> = get(<js>"my.property"</js>, <jk>false</jk>); <jc>// true</jc> 621 * 622 * <jc>// Environment variable: MY_PROPERTY=UTF-8</jc> 623 * Charset <jv>charset</jv> = get(<js>"my.property"</js>, Charset.defaultCharset()); <jc>// UTF-8</jc> 624 * 625 * <jc>// Not found, returns default</jc> 626 * String <jv>value</jv> = get(<js>"nonexistent"</js>, <js>"default"</js>); <jc>// "default"</jc> 627 * </p> 628 * 629 * @param <T> The type to convert the value to. 630 * @param name The property name. Must not be <jk>null</jk>. 631 * @param def The default value to return if not found. Must not be <jk>null</jk>. 632 * @return The found value (converted to type T), or the default value if not found. 633 * @see #get(String) 634 * @see #toType(String, Class) 635 */ 636 public <T> T get(String name, T def) { 637 reqnn("def", def); 638 reqnn("name", name); 639 var v = resolve(name); 640 if (v == null) 641 return def; 642 var t = toType(v, (Class<T>)def.getClass()); 643 return t == null ? def : t; // matches the filter(Objects::nonNull) in StringSetting.asType 644 } 645 646 /** 647 * Sets a global override for the specified property. 648 * 649 * <p> 650 * This override will apply to all threads and takes precedence over system properties. 651 * However, per-thread overrides (set via {@link #setLocal(String, String)}) will still take precedence 652 * over global overrides. 653 * 654 * <p> 655 * If the <c>juneau.settings.disableGlobal</c> system property is set to <c>true</c>, 656 * this method will throw an exception. This allows system administrators 657 * to prevent applications from overriding system properties globally. 658 * 659 * <p> 660 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the system 661 * property to return empty. Use {@link #unsetGlobal(String)} to completely remove the override. 662 * 663 * @param name The property name. Must not be <jk>null</jk>. 664 * @param value The override value, or <c>null</c> to set an empty override. 665 * @return This object for method chaining. 666 * @see #unsetGlobal(String) 667 * @see #clearGlobal() 668 */ 669 public Settings setGlobal(String name, String value) { 670 reqnn("name", name); 671 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).set(name, value); 672 return this; 673 } 674 675 /** 676 * Removes a global override for the specified property. 677 * 678 * <p> 679 * After calling this method, the property will fall back to the system property value 680 * (or per-thread override if one exists). 681 * 682 * @param name The property name. Must not be <jk>null</jk>. 683 * @see #setGlobal(String, String) 684 * @see #clearGlobal() 685 */ 686 public void unsetGlobal(String name) { 687 reqnn("name", name); 688 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).unset(name); 689 } 690 691 /** 692 * Sets a per-thread override for the specified property. 693 * 694 * <p> 695 * This override will only apply to the current thread and takes precedence over global overrides 696 * and system properties. This is particularly useful in unit tests where you need to temporarily 697 * change a system property value without affecting other threads or tests. 698 * 699 * <p> 700 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the property 701 * to return empty for this thread. Use {@link #unsetLocal(String)} to completely remove the override. 702 * 703 * @param name The property name. Must not be <jk>null</jk>. 704 * @param value The override value, or <c>null</c> to set an empty override. 705 * @return This object for method chaining. 706 * @see #unsetLocal(String) 707 * @see #clearLocal() 708 */ 709 public Settings setLocal(String name, String value) { 710 reqnn("name", name); 711 chk(nn(localStore.get()), MSG_localDisabled); 712 localStore.get().set(name, value); 713 return this; 714 } 715 716 /** 717 * Removes a per-thread override for the specified property. 718 * 719 * <p> 720 * After calling this method, the property will fall back to the global override (if set) 721 * or the system property value for the current thread. 722 * 723 * @param name The property name. Must not be <jk>null</jk>. 724 * @see #setLocal(String, String) 725 * @see #clearLocal() 726 */ 727 public void unsetLocal(String name) { 728 reqnn("name", name); 729 chk(nn(localStore.get()), MSG_localDisabled); 730 localStore.get().unset(name); 731 } 732 733 /** 734 * Clears all per-thread overrides for the current thread. 735 * 736 * <p> 737 * After calling this method, all properties will fall back to global overrides (if set) 738 * or system property values for the current thread. 739 * 740 * <p> 741 * This is typically called in a <c>@AfterEach</c> or <c>@After</c> test method to clean up 742 * thread-local overrides after a test completes. 743 * 744 * @return This object for method chaining. 745 * @see #setLocal(String, String) 746 * @see #unsetLocal(String) 747 */ 748 public Settings clearLocal() { 749 chk(nn(localStore.get()), MSG_localDisabled); 750 localStore.get().clear(); 751 return this; 752 } 753 754 /** 755 * Cleans up thread-local storage for the current thread. 756 * 757 * <p> 758 * This method should be called when a thread is finished using this Settings instance to prevent memory leaks 759 * in thread pool environments where threads are reused. 760 */ 761 public void cleanup() { 762 if (localStore != null) { 763 localStore.remove(); 764 } 765 } 766 767 /** 768 * Clears all global overrides. 769 * 770 * <p> 771 * After calling this method, all properties will fall back to resolver values 772 * (or per-thread overrides if they exist). 773 * 774 * @return This object for method chaining. 775 * @see #setGlobal(String, String) 776 * @see #unsetGlobal(String) 777 */ 778 public Settings clearGlobal() { 779 globalStore.orElseThrow(()->isex(MSG_globalDisabled)).clear(); 780 return this; 781 } 782 783 /** 784 * Converts a string to the specified type using reflection to find conversion methods or constructors. 785 * 786 * <p> 787 * This method attempts to convert a string to the specified type using the following lookup order: 788 * <ol> 789 * <li>Custom type functions registered via {@link Builder#addTypeFunction(Class, Function)}</li> 790 * <li>Special handling for {@link String} (returns the string as-is)</li> 791 * <li>Special handling for {@link Enum} types (uses {@link Enum#valueOf(Class, String)})</li> 792 * <li>Reflection lookup for static methods with signature <c>public static <T> T anyName(String arg)</c> 793 * on the target class</li> 794 * <li>Reflection lookup for static methods on the superclass (for abstract classes like {@link Charset} 795 * where concrete implementations need to use the abstract class's static method)</li> 796 * <li>Reflection lookup for public constructors with signature <c>public T(String arg)</c></li> 797 * </ol> 798 * 799 * <p> 800 * When a conversion method or constructor is found via reflection, it is cached in the 801 * <c>toTypeFunctions</c> map for future use. 802 * 803 * <h5 class='section'>Examples:</h5> 804 * <ul class='spaced-list'> 805 * <li><c>Boolean</c> - Uses <c>Boolean.valueOf(String)</c> static method 806 * <li><c>Integer</c> - Uses <c>Integer.valueOf(String)</c> static method 807 * <li><c>Charset</c> - Uses <c>Charset.forName(String)</c> static method (even for concrete implementations) 808 * <li><c>File</c> - Uses <c>File(String)</c> constructor 809 * </ul> 810 * 811 * @param <T> The target type. 812 * @param s The string to convert. Must not be <jk>null</jk>. 813 * @param c The target class. Must not be <jk>null</jk>. 814 * @return The converted value. 815 * @throws RuntimeException If the type is not supported for conversion (no static method or constructor found). 816 */ 817 @SuppressWarnings({ 818 "rawtypes" // Raw types necessary for generic type handling 819 }) 820 public <T> T toType(String s, Class<T> c) { 821 reqnn("s", s); 822 reqnn("c", c); 823 var f = (Function<String,T>)toTypeFunctions.get(c); 824 if (f == null) { 825 if (c == String.class) 826 return (T)s; 827 if (c.isEnum()) 828 return (T)Enum.valueOf((Class<? extends Enum>)c, s); 829 ClassInfoTyped<T> ci = info(c); 830 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); 831 // Also accept `static T parse(CharSequence)` etc. — common for java.time types (Instant, 832 // LocalDate, Duration) whose static factories declare the wider CharSequence parameter. 833 if (f == null) 834 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); 835 if (f == null) 836 f = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).map(x -> (Function<String,T>)x::newInstance).orElse(null); 837 if (f != null) 838 toTypeFunctions.putIfAbsent(c, f); 839 } 840 if (f == null) 841 throw rex("Invalid env type: %s", c); 842 return f.apply(s); 843 } 844} 845