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}