001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2018–2021 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.microprofile.config; 018 019import java.io.BufferedReader; 020import java.io.Closeable; 021import java.io.IOException; 022import java.io.InputStreamReader; 023import java.io.Reader; 024import java.io.Serializable; 025 026import java.lang.reflect.Type; 027 028import java.net.URL; 029 030import java.nio.charset.StandardCharsets; 031 032import java.security.AccessController; 033import java.security.PrivilegedAction; 034 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.Enumeration; 039import java.util.Iterator; // for javadoc only 040import java.util.List; 041import java.util.LinkedList; 042import java.util.NoSuchElementException; 043import java.util.Objects; 044import java.util.Optional; 045import java.util.Properties; 046import java.util.ServiceLoader; 047import java.util.Set; 048import java.util.TreeSet; 049 050import org.eclipse.microprofile.config.spi.ConfigSource; 051import org.eclipse.microprofile.config.spi.ConfigSourceProvider; 052import org.eclipse.microprofile.config.spi.Converter; 053 054/** 055 * A {@link Serializable} implementation of the {@link 056 * org.eclipse.microprofile.config.Config} interface that is also 057 * a {@link Closeable} {@link TypeConverter}. 058 * 059 * @author <a href="https://about.me/lairdnelson" 060 * target="_parent">Laird Nelson</a> 061 * 062 * @see org.eclipse.microprofile.config.Config 063 */ 064public final class Config implements Closeable, org.eclipse.microprofile.config.Config, Serializable, TypeConverter { 065 066 private static final long serialVersionUID = 1L; 067 068 private final TypeConverter typeConverter; 069 070 private final Collection<ConfigSource> sources; 071 072 private volatile boolean closed; 073 074 /** 075 * Creates a new {@link Config} with a <a 076 * href="https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.3/org/eclipse/microprofile/config/package-summary.html#package.description" 077 * target="_parent">default set of {@link ConfigSource}s</a> and a 078 * {@linkplain Converter default set of <code>Converter</code>s} 079 * (including discovered {@link Converter}s). 080 * 081 * @exception IOException if an error occurs while reading a 082 * {@code META-INF/microprofile-config.properties} resource 083 * 084 * @exception java.util.ServiceConfigurationError if there is a 085 * problem interacting with a {@link ServiceLoader} 086 */ 087 public Config() throws IOException { 088 super(); 089 final List<ConfigSource> sources = new LinkedList<>(); 090 final Collection<? extends ConfigSource> defaultConfigSources = getDefaultConfigSources(); 091 final Collection<? extends ConfigSource> discoveredConfigSources = getDiscoveredConfigSources(null); 092 sources.addAll(defaultConfigSources); 093 sources.addAll(discoveredConfigSources); 094 Collections.sort(sources, ConfigSourceComparator.INSTANCE); 095 this.sources = Collections.unmodifiableCollection(sources); 096 this.typeConverter = new ConversionHub(); 097 } 098 099 /** 100 * Creates a new {@link Config} instance. 101 * 102 * <p>The MicroProfile Config specification wants {@link 103 * org.eclipse.microprofile.config.Config} implementations to be 104 * acquired using {@link 105 * org.eclipse.microprofile.config.ConfigProvider#getConfig()} 106 * invocations. This constructor exists primarily for 107 * convenience.</p> 108 * 109 * <h2>Thread Safety</h2> 110 * 111 * <p><strong>{@code sources} will be synchronized on and iterated 112 * over by this constructor</strong>, which may have implications on 113 * the type of {@link Collection} supplied.</p> 114 * 115 * @param sources a {@link Collection} of {@link ConfigSource}s; may 116 * be {@code null}; <strong>will be synchronized on and iterated 117 * over</strong>; copied by value; no reference is retained to this 118 * object 119 * 120 * @param typeConverter a {@link TypeConverter} implementation used 121 * for type conversion; must not be {@code null}; if it implements 122 * {@link Closeable} <strong>it will be {@linkplain 123 * Closeable#close() closed} by this {@link Config}'s {@link 124 * #close()} method</strong> 125 * 126 * @exception NullPointerException of {@code typeConverter} is 127 * {@code null} 128 * 129 * @see ConfigProviderResolver 130 * 131 * @see org.eclipse.microprofile.config.ConfigProvider#getConfig() 132 */ 133 public Config(final Collection<? extends ConfigSource> sources, 134 final TypeConverter typeConverter) { 135 super(); 136 this.typeConverter = Objects.requireNonNull(typeConverter); 137 if (sources == null) { 138 this.sources = Collections.emptySet(); 139 } else { 140 synchronized (sources) { 141 if (sources.isEmpty()) { 142 this.sources = Collections.emptySet(); 143 } else { 144 this.sources = Collections.unmodifiableCollection(sources); 145 } 146 } 147 } 148 } 149 150 /** 151 * Closes this {@link Config} using a best-effort strategy. 152 * 153 * <p>An attempt is made to close each {@link ConfigSource} that is 154 * itself an instance of {@link Closeable} and that was supplied to 155 * this {@link Config} at construction time. If any errors occur 156 * along the way, they are added as {@linkplain 157 * Throwable#addSuppressed(Throwable) suppressed exceptions} to an 158 * {@link IOException} which is thrown as a kind of 159 * aggregate.</p> 160 * 161 * <p>When this method finishes normally, all of this {@link 162 * Config}'s associated {@link Closeable} {@link ConfigSource}s will 163 * be {@linkplain Closeable#close() closed}. <strong>In addition, 164 * the {@link TypeConverter} supplied at construction time will be 165 * closed as well if it implements {@link Closeable}.</strong></p> 166 * 167 * <h2>Thread Safety</h2> 168 * 169 * <p>This method is idempotent and safe for concurrent use by 170 * multiple threads.</p> 171 * 172 * @exception IOException if at least one {@link Closeable} {@link 173 * ConfigSource} was not successfully closed or if the {@link 174 * TypeConverter} supplied at construction time implements {@link 175 * Closeable} and threw an {@link IOException} 176 */ 177 @Override 178 public final void close() throws IOException { 179 final boolean oldClosed = this.isClosed(); 180 if (!oldClosed) { 181 IOException throwMe = null; 182 synchronized (this.sources) { 183 for (final ConfigSource configSource : this.sources) { 184 if (configSource instanceof Closeable) { 185 try { 186 ((Closeable)configSource).close(); 187 } catch (final IOException ioException) { 188 if (throwMe == null) { 189 throwMe = ioException; 190 } else { 191 throwMe.addSuppressed(ioException); 192 } 193 } 194 } 195 } 196 } 197 if (this.typeConverter instanceof Closeable) { 198 try { 199 ((Closeable)this.typeConverter).close(); 200 } catch (final IOException ioException) { 201 if (throwMe == null) { 202 throwMe = ioException; 203 } else { 204 throwMe.addSuppressed(ioException); 205 } 206 } 207 } 208 if (throwMe != null) { 209 throw throwMe; 210 } 211 this.closed = true; 212 ConfigProviderResolver.instance().releaseConfig(this); // XXX re-entrant call, potentially 213 } 214 } 215 216 /** 217 * Returns {@code true} if this {@link Config} has been {@linkplain 218 * #close() closed}. 219 * 220 * <p>A closed {@link Config} will throw {@link 221 * IllegalStateException} from most of its methods.</p> 222 * 223 * <h2>Thread Safety</h2> 224 * 225 * <p>This method is safe for use by multiple threads.</p> 226 * 227 * @return {@code true} if this {@link Config} has been {@linkplain 228 * #close() closed}; {@code false} otherwise 229 * 230 * @see #close() 231 */ 232 public final boolean isClosed() { 233 return this.closed; 234 } 235 236 /** 237 * Returns an {@link Iterable} representing a snapshot at a moment 238 * in time of this {@link Config}'s affiliated {@link ConfigSource}s 239 * as they existed at that time. 240 * 241 * <p>This method never returns {@code null}.</p> 242 * 243 * <p>The underlying collection of {@link ConfigSource}s the 244 * returned {@link Iterable} is capable of iterating over is an 245 * immutable copy of the internal collection of {@link 246 * ConfigSource}s managed by this {@link Config}.</p> 247 * 248 * <p>The {@link Iterable} returned by this method {@linkplain 249 * Iterable#iterator() creates <code>Iterator</code>s} that do not 250 * support the {@link Iterator#remove()} method.</p> 251 * 252 * <p>The MicroProfile Config specification implies a state of 253 * affairs that permits {@link ConfigSource}s "inside" a {@link 254 * Config} to come and go. Consequently, this method returns what 255 * is effectively a dissociated snapshot at a moment in time of a 256 * collection of {@link ConfigSource}s that were once managed by 257 * this {@link Config} at that moment in time.</p> 258 * 259 * <h2>Thread Safety</h2> 260 * 261 * <p>This method is safe for use by multiple threads.</p> 262 * 263 * <p>The {@link Iterable} returned by this method is safe for use 264 * by multiple threads.</p> 265 * 266 * @return an {@link Iterable} of {@link ConfigSource}s; never 267 * {@code null} 268 * 269 * @exception IllegalStateException if this {@link Config} has been 270 * {@linkplain #close() closed} 271 */ 272 @Override 273 public final Iterable<ConfigSource> getConfigSources() { 274 if (this.isClosed()) { 275 throw new IllegalStateException("this.isClosed()"); 276 } 277 final Iterable<ConfigSource> returnValue; 278 synchronized (this.sources) { 279 if (this.sources.isEmpty()) { 280 returnValue = Collections.emptySet(); 281 } else { 282 returnValue = Collections.unmodifiableCollection(new ArrayList<>(this.sources)); 283 } 284 } 285 return returnValue; 286 } 287 288 /** 289 * Returns an {@link Iterable} representing a snapshot at a moment 290 * in time of the configuration property names as they existed at 291 * that time. 292 * 293 * <p>This method never returns {@code null}.</p> 294 * 295 * <p>The underlying collection of property names the returned 296 * {@link Iterable} is capable of iterating over is an immutable 297 * copy of the internal collection of configuration property names 298 * managed by this {@link Config}.</p> 299 * 300 * <p>The {@link Iterable} returned by this method {@linkplain 301 * Iterable#iterator() creates <code>Iterator</code>s} that do not 302 * support the {@link Iterator#remove()} method.</p> 303 * 304 * <p>The MicroProfile Config specification implies a state of 305 * affairs that permits {@link ConfigSource}s "inside" a {@link 306 * Config} to come and go. Consequently, this method returns what 307 * is effectively a dissociated snapshot at a moment in time of a 308 * collection of the configuration property names that were once 309 * managed by this {@link Config} at that moment in time.</p> 310 * 311 * <p>No caching is performed by this method. Property names are 312 * harvested from calls to {@link ConfigSource#getPropertyNames()} 313 * on each {@link ConfigSource} present in the {@link Iterable} 314 * returned by the {@link #getConfigSources()} method.</p> 315 * 316 * <h2>Thread Safety</h2> 317 * 318 * <p>This method is safe for use by multiple threads.</p> 319 * 320 * <p>The {@link Iterable} returned by this method is safe for use 321 * by multiple threads.</p> 322 * 323 * @return an {@link Iterable} representing a snapshot of a 324 * collection of configuration property names which this {@link 325 * Config} manages; never {@code null} 326 * 327 * @exception IllegalStateException if this {@link Config} has been 328 * {@linkplain #close() closed} 329 */ 330 @Override 331 public final Iterable<String> getPropertyNames() { 332 final Iterable<String> returnValue; 333 final Iterable<ConfigSource> configSources = this.getConfigSources(); 334 if (configSources == null) { 335 returnValue = Collections.emptySet(); 336 } else { 337 final Set<String> names = new TreeSet<>(); 338 for (final ConfigSource configSource : configSources) { 339 if (configSource != null) { 340 // The specification says nothing about concurrency. 341 synchronized (configSource) { 342 final Collection<? extends String> sourceNames = configSource.getPropertyNames(); 343 if (sourceNames != null && !sourceNames.isEmpty()) { 344 names.addAll(sourceNames); 345 } 346 } 347 } 348 } 349 returnValue = Collections.unmodifiableSet(names); 350 } 351 assert returnValue != null; 352 return returnValue; 353 } 354 355 /** 356 * Returns an {@link Optional} representing an optional 357 * configuration property value for the supplied {@code name}, as 358 * converted to an object of the supplied {@code type}. 359 * 360 * <p>This method never returns {@code null}.</p> 361 * 362 * <p>This method does not cache the value it returns.</p> 363 * 364 * <p>It is worth noting that the MicroProfile Config 365 * specification does not say anything about whether {@link 366 * Converter}s are allowed to attempt to "convert" {@code null} 367 * values from {@link ConfigSource#getValue(String)} invocations 368 * into non-{@code null} objects. The <a 369 * href="https://github.com/eclipse/microprofile-config/tree/master/tck" 370 * target="_parent">MicroProfile Config TCK</a> will fail if {@link 371 * Converter}s _do_ attempt to work on {@code null} values, so this 372 * implementation never uses a {@code null} value for 373 * conversion.</p> 374 * 375 * <h2>Thread Safety</h2> 376 * 377 * <p>This method is safe for use by multiple threads.</p> 378 * 379 * @param <T> the type of the value being requested 380 * 381 * @param name the name of the configuration property whose 382 * converted value should be returned; may be {@code null}; passed 383 * unaltered to {@link ConfigSource#getValue(String)} so subject to 384 * that method's (unspecified) preconditions 385 * 386 * @param type the {@link Class} representing the type any 387 * non-{@code null} value should be converted to if possible; must 388 * not be {@code null} 389 * 390 * @return an {@link Optional} representing an optional 391 * configuration property value for the supplied {@code name}, as 392 * converted to an object of the supplied {@code type}; never {@code 393 * null} 394 * 395 * @exception IllegalArgumentException if the value could not be 396 * converted to the requested type 397 * 398 * @exception IllegalStateException if this {@link Config} has been 399 * {@linkplain #close() closed} 400 * 401 * @see #getOptionalValue(String, Type) 402 * 403 * @see #getValue(String, Class) 404 * 405 * @see #getValue(String, Type) 406 * 407 * @see ConfigSource#getValue(String) 408 * 409 * @see TypeConverter#convert(String, Type) 410 * 411 * @see Converter#convert(String) 412 */ 413 @Override 414 public final <T> Optional<T> getOptionalValue(final String name, final Class<T> type) { 415 return this.getOptionalValue(name, (Type)type); 416 } 417 418 /** 419 * Returns an {@link Optional} representing an optional 420 * configuration property value for the supplied {@code name}, as 421 * converted to an object of the supplied {@code type}. 422 * 423 * <p>This method never returns {@code null}.</p> 424 * 425 * <p>This method does not cache the value it returns.</p> 426 * 427 * <h2>Thread Safety</h2> 428 * 429 * <p>This method is safe for use by multiple threads.</p> 430 * 431 * @param <T> the type of the value being requested 432 * 433 * @param name the name of the configuration property whose 434 * converted value should be returned; may be {@code null}; passed 435 * unaltered to {@link ConfigSource#getValue(String)} so subject to 436 * that method's (unspecified) preconditions 437 * 438 * @param type the {@link Type} representing the type any 439 * non-{@code null} value should be converted to if possible; must 440 * not be {@code null} 441 * 442 * @return an {@link Optional} representing an optional 443 * configuration property value for the supplied {@code name}, as 444 * converted to an object of the supplied {@code type}; never {@code 445 * null} 446 * 447 * @exception IllegalArgumentException if the value could not be 448 * converted to the requested type 449 * 450 * @exception IllegalStateException if this {@link Config} has been 451 * {@linkplain #close() closed} 452 * 453 * @see #getOptionalValue(String, Class) 454 * 455 * @see #getValue(String, Class) 456 * 457 * @see #getValue(String, Type) 458 * 459 * @see ConfigSource#getValue(String) 460 * 461 * @see TypeConverter#convert(String, Type) 462 * 463 * @see Converter#convert(String) 464 */ 465 public final <T> Optional<T> getOptionalValue(final String name, final Type type) { 466 Objects.requireNonNull(type); 467 Optional<T> returnValue = null; 468 final Iterable<ConfigSource> configSources = this.getConfigSources(); 469 if (configSources != null) { 470 for (final ConfigSource configSource : configSources) { 471 if (configSource != null) { 472 final String value = configSource.getValue(name); 473 if (value != null) { 474 returnValue = Optional.of(this.convert(value, type)); 475 assert returnValue != null; 476 if (returnValue.isPresent()) { 477 break; 478 } 479 } 480 } 481 } 482 } 483 if (returnValue == null) { 484 returnValue = Optional.empty(); 485 } 486 return returnValue; 487 } 488 489 /** 490 * Returns the value for the configuration property named by the 491 * supplied {@code name}, as converted to an object of the supplied 492 * {@code type}. 493 * 494 * <p>This method never returns {@code null}.</p> 495 * 496 * <p>This method does not cache the value it returns.</p> 497 * 498 * <p>The MicroProfile Config specification does not say whether 499 * implementations of the {@link 500 * org.eclipse.microprofile.config.Config#getValue(String, Class)} 501 * method may or may not return {@code null}. {@code null} values 502 * from {@link ConfigSource#getValue(String)} invocations are taken 503 * to indicate a given configuration property value's absence, 504 * however. Coupled with the fact that all {@link 505 * org.eclipse.microprofile.config.Config#getValue(String, Class)} 506 * are required to throw {@link NoSuchElementException}s when "the 507 * property isn't present in the configuration", this implementation 508 * chooses never to return {@code null}.</p> 509 * 510 * <p>It is also worth noting that the MicroProfile Config 511 * specification does not say anything about whether {@link 512 * Converter}s are allowed to attempt to "convert" {@code null} 513 * values from {@link ConfigSource#getValue(String)} invocations 514 * into non-{@code null} objects. The <a 515 * href="https://github.com/eclipse/microprofile-config/tree/master/tck" 516 * target="_parent">MicroProfile Config TCK</a> will fail if {@link 517 * Converter}s _do_ attempt to work on {@code null} values, so this 518 * implementation never uses a {@code null} value for 519 * conversion.</p> 520 * 521 * <h2>Thread Safety</h2> 522 * 523 * <p>This method is safe for use by multiple threads.</p> 524 * 525 * @param <T> the type of the values returned by this method 526 * 527 * @param name the name of the configuration property whose value 528 * should be returned; may be {@code null}; passed unaltered to 529 * {@link ConfigSource#getValue(String)} so subject to that method's 530 * (unspecified) preconditions 531 * 532 * @param type the {@link Class} representing the type any 533 * non-{@code null} value should be converted to if possible; must 534 * not be {@code null} 535 * 536 * @return the converted value; never {@code null} 537 * 538 * @exception NoSuchElementException if the requested configuration 539 * property value does not exist 540 * 541 * @exception IllegalStateException if this {@link Config} has been 542 * {@linkplain #close() closed} 543 * 544 * @see ConfigSource#getValue(String) 545 * 546 * @see #getValue(String, Type) 547 * 548 * @see #getOptionalValue(String, Class) 549 * 550 * @see #getOptionalValue(String, Type) 551 */ 552 @Override 553 public final <T> T getValue(final String name, final Class<T> type) { 554 return this.getValue(name, (Type)type); 555 } 556 557 /** 558 * Returns the value for the configuration property named by the 559 * supplied {@code name}, as converted to an object of the supplied 560 * {@code type}. 561 * 562 * <p>This method never returns {@code null}.</p> 563 * 564 * <p>This method does not cache the value it returns.</p> 565 * 566 * <p>It is also worth noting that the MicroProfile Config 567 * specification does not say anything about whether {@link 568 * Converter}s are allowed to attempt to "convert" {@code null} 569 * values from {@link ConfigSource#getValue(String)} invocations 570 * into non-{@code null} objects. The <a 571 * href="https://github.com/eclipse/microprofile-config/tree/master/tck" 572 * target="_parent">MicroProfile Config TCK</a> will fail if {@link 573 * Converter}s _do_ attempt to work on {@code null} values, so this 574 * implementation never uses a {@code null} value for 575 * conversion.</p> 576 * 577 * <h2>Thread Safety</h2> 578 * 579 * <p>This method is safe for use by multiple threads.</p> 580 * 581 * @param <T> the type of the values returned by this method 582 * 583 * @param name the name of the configuration property whose value 584 * should be returned; may be {@code null}; passed unaltered to 585 * {@link ConfigSource#getValue(String)} so subject to that method's 586 * (unspecified) preconditions 587 * 588 * @param type the {@link Type} representing the type any 589 * non-{@code null} value should be converted to if possible; must 590 * not be {@code null} 591 * 592 * @return the converted value; never {@code null} 593 * 594 * @exception NoSuchElementException if the requested configuration 595 * property value does not exist 596 * 597 * @exception IllegalStateException if this {@link Config} has been 598 * {@linkplain #close() closed} 599 * 600 * @see ConfigSource#getValue(String) 601 * 602 * @see #getValue(String, Class) 603 * 604 * @see #getOptionalValue(String, Class) 605 * 606 * @see #getOptionalValue(String, Type) 607 */ 608 public final <T> T getValue(final String name, final Type type) { 609 Objects.requireNonNull(type); 610 T returnValue = null; 611 final Iterable<ConfigSource> configSources = this.getConfigSources(); 612 if (configSources != null) { 613 for (final ConfigSource configSource : configSources) { 614 if (configSource != null) { 615 final String value = configSource.getValue(name); 616 if (value != null) { 617 returnValue = this.convert(value, type); 618 if (returnValue != null) { 619 break; 620 } 621 } 622 } 623 } 624 } 625 if (returnValue == null) { 626 throw new NoSuchElementException(name); 627 } 628 return returnValue; 629 } 630 631 /** 632 * Implements the {@link TypeConverter#convert(String, Type)} method 633 * by invoking the same method on this {@link Config}'s affiliated 634 * {@link TypeConverter} supplied at construction time and returning 635 * the result. 636 * 637 * <p>This method may return {@code null}.</p> 638 * 639 * <h2>Thread Safety</h2> 640 * 641 * <p>This method is safe for use by multiple threads.</p> 642 * 643 * @param rawValue the value to convert; may be {@code null} 644 * 645 * @param type the {@link Type} to which the current invocation of 646 * this method's return value should be assignable; must not be 647 * {@code null} 648 * 649 * @return an object assignable to the supplied {@code type}, or 650 * {@code null} 651 * 652 * @see TypeConverter#convert(String, Type) 653 * 654 * @exception NullPointerException if {@code type} is {@code null} 655 * 656 * @exception IllegalArgumentException if conversion could not occur 657 * for any reason 658 * 659 * @exception IllegalStateException if this {@link Config} has been 660 * {@linkplain #close() closed} 661 */ 662 @Override 663 public final <T> T convert(final String rawValue, final Type type) { 664 if (this.isClosed()) { 665 throw new IllegalStateException("this.isClosed()"); 666 } 667 return this.typeConverter.convert(rawValue, type); 668 } 669 670 static final Collection<? extends ConfigSource> getDefaultConfigSources() throws IOException { 671 return getDefaultConfigSources(null); 672 } 673 674 static final Collection<? extends ConfigSource> getDefaultConfigSources(final ClassLoader classLoader) throws IOException { 675 final Collection<ConfigSource> sources = new LinkedList<>(); 676 sources.add(new SystemPropertiesConfigSource()); 677 sources.add(new EnvironmentVariablesConfigSource()); 678 final Collection<? extends ConfigSource> microprofileConfigPropertiesConfigSources = getMicroprofileConfigPropertiesSources(classLoader); 679 if (microprofileConfigPropertiesConfigSources != null && !microprofileConfigPropertiesConfigSources.isEmpty()) { 680 sources.addAll(microprofileConfigPropertiesConfigSources); 681 } 682 return Collections.unmodifiableCollection(sources); 683 } 684 685 static final Collection<? extends ConfigSource> getMicroprofileConfigPropertiesSources(ClassLoader classLoader) throws IOException { 686 if (classLoader == null) { 687 classLoader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)() -> Thread.currentThread().getContextClassLoader()); 688 } 689 final Enumeration<? extends URL> urls = classLoader.getResources("META-INF/microprofile-config.properties"); 690 Collection<ConfigSource> returnValue = new ArrayList<>(); 691 if (urls != null) { 692 while (urls.hasMoreElements()) { 693 final URL url = urls.nextElement(); 694 if (url != null) { 695 final Properties properties = new Properties(); 696 // The specification does not mandate a character set for 697 // the /META-INF/microprofile-config.properties, nor whether 698 // it should be in java.util.Properties format. We'll 699 // assume ISO-8859-1 and java.util.Properties format. 700 try (final Reader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.ISO_8859_1))) { 701 properties.load(reader); 702 } 703 // The specification provides no guidance on ConfigSource naming. 704 returnValue.add(new PropertiesConfigSource(properties, url.toString())); 705 } 706 } 707 } 708 if (returnValue.isEmpty()) { 709 returnValue = Collections.emptySet(); 710 } else { 711 returnValue = Collections.unmodifiableCollection(returnValue); 712 } 713 return returnValue; 714 } 715 716 static final Collection<? extends ConfigSource> getDiscoveredConfigSources(ClassLoader classLoader) { 717 if (classLoader == null) { 718 classLoader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)() -> Thread.currentThread().getContextClassLoader()); 719 } 720 final ServiceLoader<ConfigSource> discoveredSources = ServiceLoader.load(ConfigSource.class, classLoader); 721 assert discoveredSources != null; 722 final Collection<ConfigSource> sources = new LinkedList<>(); 723 for (final ConfigSource source : discoveredSources) { 724 if (source != null) { 725 sources.add(source); 726 } 727 } 728 final ServiceLoader<ConfigSourceProvider> discoveredConfigSourceProviders = ServiceLoader.load(ConfigSourceProvider.class, classLoader); 729 assert discoveredConfigSourceProviders != null; 730 for (final ConfigSourceProvider provider : discoveredConfigSourceProviders) { 731 if (provider != null) { 732 final Iterable<? extends ConfigSource> configSources = provider.getConfigSources(classLoader); 733 if (configSources != null) { 734 for (final ConfigSource configSource : configSources) { 735 if (configSource != null) { 736 sources.add(configSource); 737 } 738 } 739 } 740 } 741 } 742 return Collections.unmodifiableCollection(sources); 743 } 744}