All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.microbean.microprofile.config.ConfigProviderResolver Maven / Gradle / Ivy

The newest version!
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright © 2018–2021 microBean™.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package org.microbean.microprofile.config;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

import java.security.AccessController;
import java.security.PrivilegedAction;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.WeakHashMap;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider; // for javadoc only

/**
 * An {@link AutoCloseable} implementation of the abstract {@link
 * org.eclipse.microprofile.config.spi.ConfigProviderResolver} class.
 *
 * 

Thread Safety

* *

This class is safe for concurrent use by multiple threads.

* * @author Laird Nelson * * @see org.eclipse.microprofile.config.spi.ConfigProviderResolver */ public final class ConfigProviderResolver extends org.eclipse.microprofile.config.spi.ConfigProviderResolver implements AutoCloseable { // @GuardedBy("self") private final WeakHashMap> configMap; private final ReferenceQueue autoClosingReferenceQueue; /** * Creates a new {@link ConfigProviderResolver}. */ public ConfigProviderResolver() { super(); this.configMap = new WeakHashMap<>(); this.autoClosingReferenceQueue = new ReferenceQueue<>(); final Thread cleaner = new AutoCloseableCloserThread(this.autoClosingReferenceQueue); cleaner.start(); } /** * Closes this {@link ConfigProviderResolver} using a best-effort * strategy. * *

This method attempts to {@linkplain #releaseConfig(Config) * release} each of the {@link AutoCloseable} {@link Config} * instances that are {@linkplain #registerConfig(Config, * ClassLoader) registered} with it. If any exception occurs, it is * aggregated with any others and thrown at the end of the whole * process.

* *

Thread Safety

* *

This method is idempotent and safe for concurrent use by * multiple threads.

* * @exception RuntimeException if an error occurs * * @see #registerConfig(Config, ClassLoader) * * @see #releaseConfig(Config) */ @Override public final void close() { synchronized (this.configMap) { if (!this.configMap.isEmpty()) { // Remember that configMap can technically be shrinking all // the time, even with the lock held. final Collection> configsSnapshot = new HashSet<>(this.configMap.values()); if (!configsSnapshot.isEmpty()) { RuntimeException throwMe = null; for (final WeakAutoCloseableReference weakConfigReference : configsSnapshot) { assert weakConfigReference != null; final Config config = weakConfigReference.getPotentialAutoCloseable(); if (config != null) { try { this.releaseConfig(config); } catch (final RuntimeException runtimeException) { if (throwMe == null) { throwMe = runtimeException; } else { throwMe.addSuppressed(runtimeException); } } catch (final Exception exception) { if (throwMe == null) { throwMe = new RuntimeException(exception); } else { throwMe.addSuppressed(exception); } } } } if (throwMe != null) { throw throwMe; } } } } } /** * Creates and returns a new {@link * org.eclipse.microprofile.config.spi.ConfigBuilder}. * *

This method never returns {@code null}.

* *

Thread Safety

* *

This method is safe for concurrent use by multiple threads.

* *

The {@link ConfigBuilder} implementation is safe for * concurrent use by multiple threads.

* *

Implementation Notes

* *

Because of the requirement of having an implementation of the * {@link ConfigBuilder#forClassLoader(ClassLoader)} method, the * {@link ConfigBuilder} implementation returned by this method may * contain a strong reference to a {@link ClassLoader} supplied to * it by that method. The programmer needs to take care that this * does not result in classloader leaks. In general, {@link * ConfigBuilder}s should be retained for only as long as is * necessary to {@linkplain ConfigBuilder#build() build} a {@link * Config} and no longer.

* * @return a new {@link * org.eclipse.microprofile.config.spi.ConfigBuilder}; never {@code * null} */ @Override public final org.eclipse.microprofile.config.spi.ConfigBuilder getBuilder() { return new ConfigBuilder(); } /** * Returns the sole {@link Config} instance appropriate for the * {@linkplain Thread#getContextClassLoader() context * ClassLoader}, creating and building a default one * just in time if necessary. * *

Thread Safety

* *

This method is safe for concurrent use by multiple threads.

* *

This method never returns {@code null}.

* * @return a {@link Config} instance; never {@code null} * * @see #getConfig(ClassLoader) * * @see Thread#getContextClassLoader() * * @see * org.eclipse.microprofile.config.ConfigProvider#getConfig() */ @Override public final Config getConfig() { return this.getConfig(AccessController.doPrivileged((PrivilegedAction)() -> Thread.currentThread().getContextClassLoader())); } /** * Returns the sole {@link Config} instance appropriate for the * supplied {@link ClassLoader}, creating and building a default one * just in time if necessary. * *

Thread Safety

* *

This method is safe for concurrent use by multiple threads.

* *

Implementation Notes

* *

The specification does not indicate what to do if the supplied * {@link ClassLoader} is {@code null}, but spread throughout other * areas of the specification {@linkplain ConfigProvider#getConfig() * there are implications} that a {@code null} {@link ClassLoader} * means that the current thread's {@linkplain * Thread#getContextClassLoader() context classloader} should be * used instead. This implementation follows those implications. * See issue 426 for more details. * * @param classLoader the {@link ClassLoader} used to identify the * {@link Config} to return; may be {@code null} in which case the * {@linkplain Thread#getContextClassLoader() context * ClassLoader} will be used instead * * @return a {@link Config} instance; never {@code null} * * @see #getBuilder() * * @see #getConfig() * * @see MicroProfile Config issue 426 */ @Override public final Config getConfig(ClassLoader classLoader) { if (classLoader == null) { // See https://github.com/eclipse/microprofile-config/issues/426 classLoader = AccessController.doPrivileged((PrivilegedAction)() -> Thread.currentThread().getContextClassLoader()); } Config returnValue = null; synchronized (this.configMap) { final WeakAutoCloseableReference configReference = this.configMap.get(classLoader); if (configReference != null) { returnValue = configReference.getPotentialAutoCloseable(); } if (returnValue == null) { returnValue = this.getBuilder() .addDefaultSources() .addDiscoveredSources() .addDiscoveredConverters() .forClassLoader(classLoader) .build(); assert returnValue != null; // Deliberately called with the lock held on the configMap this.registerConfig(returnValue, classLoader); } } return returnValue; } /** * Registers the supplied {@link Config} instance under the supplied * {@link ClassLoader} in some way. * *

Thread Safety

* *

This method is safe for concurrent use by multiple threads.

* *

Implementation Notes

* *

The specification says:

* *
If the ClassLoader is null then the current * Application will be used.
* *

This implementation assumes "current Application" is * equivalent to "current thread's {@linkplain * Thread#getContextClassLoader() context * ClassLoader}". See issue 426 for more details.

* * @param config the {@link Config} to register; may be {@code null} * in which case no action will be taken * * @param classLoader the {@link ClassLoader} to use as the key * under which the supplied {@link Config} should be registered; if * {@code null} then the return value of {@link * Thread#getContextClassLoader()} will be used instead * * @see * org.eclipse.microprofile.config.spi.ConfigProviderResolver#registerConfig(Config, * ClassLoader) * * @see MicroProfile Config issue 426 */ @Override public final void registerConfig(final Config config, ClassLoader classLoader) { if (config != null) { if (classLoader == null) { // See https://github.com/eclipse/microprofile-config/issues/426 classLoader = AccessController.doPrivileged((PrivilegedAction)() -> Thread.currentThread().getContextClassLoader()); } final WeakAutoCloseableReference configReference = new WeakAutoCloseableReference<>(classLoader, config, this.autoClosingReferenceQueue); synchronized (this.configMap) { final WeakAutoCloseableReference oldConfigReference = this.configMap.putIfAbsent(classLoader, configReference); if (oldConfigReference != null) { final Config oldConfig = oldConfigReference.getPotentialAutoCloseable(); if (oldConfig != null) { // The specification says that an IllegalStateException // should be thrown "if there is already a Config registered // within the Application." It is not entirely clear what // "within the Application" means. We do the best we can // here. throw new IllegalStateException("configMap.containsKey(" + classLoader + "): " + oldConfig); } } } } } /** * Releases the supplied {@link Config} by {@linkplain * AutoCloseable#close() closing} it if it implements {@link * AutoCloseable} and atomically removes all of its {@linkplain * #registerConfig(Config, ClassLoader) registrations}. * *

An {@link AutoCloseable} {@link Config} implementation * released by this method becomes effectively unusable by design * after this method completes.

* *

In general, owing to the underspecified nature of this method, * end users should probably not call it directly.

* *

Thread Safety

* *

This method is safe for concurrent use by multiple threads.

* *

Implementation Notes

* *

The specification says:

* *
A Config normally gets released if the Application it * is associated with gets destroyed.
* *

This implementation assumes the previous sentence means that * if a {@link Config} was previously {@linkplain * #registerConfig(Config, ClassLoader) registered} under a {@link * ClassLoader} A, and under a {@link ClassLoader} * B, then both registrations will be deleted. The * equivalence between "Application" and {@link ClassLoader} is * derived from the documentation for the {@link * ConfigProvider#getConfig()} method, where there is an implication * that the {@linkplain Thread#getContextClassLoader() context * classloader} will be used as the key under which the {@link * Config} to be returned may be found.

* *

{@linkplain ConfigProvider#getConfig(ClassLoader) Elsewhere in * the specification}, it is stated:

* *
There is exactly a single Config instance per * ClassLoader[.]
* *

This is of course false since there may be zero {@link Config} * instances per {@link ClassLoader}. Leaving that aside, this * sentence also does not say whether it is permitted for a single * {@link Config} instance to be shared between two {@link * ClassLoader}s. This implementation permits such perhaps edge * cases, but a call to this {@link #releaseConfig(Config)} method * will remove both such registrations if present.

* *

The specification has no guidance on what to do if a {@link * Config} is released, and then another one is acquired via {@link * ConfigProvider#getConfig(ClassLoader)}, if anything. This * implementation does not track {@link Config} instances once they * have been released.

* *

The specification does not indicate whether the supplied * {@link Config} must be non-{@code null}. This implementation * consequently accepts {@code null} {@link Config}s and does * nothing in such cases.

* *

This method is called by the {@link #close()} method. * * @param config the {@link Config} to release; may be {@code null} * in which case no action will be taken * * @see #close() * * @see #registerConfig(Config, ClassLoader) */ @Override public final void releaseConfig(final Config config) { // The specification says nothing about whether arguments can be null. if (config != null) { RuntimeException throwMe = null; synchronized (this.configMap) { if (!this.configMap.isEmpty()) { final Collection>> entrySet = this.configMap.entrySet(); if (entrySet != null && !entrySet.isEmpty()) { final Iterator>> entryIterator = entrySet.iterator(); if (entryIterator != null) { while (entryIterator.hasNext()) { Entry> entry = null; try { entry = entryIterator.next(); } catch (final NoSuchElementException noSuchElementException) { // This can happen legally because we're working // with a WeakHashMap, so keys are effectively being // removed by the garbage collector at any point. } if (entry != null) { final WeakAutoCloseableReference configReference = entry.getValue(); if (configReference != null) { final Config existingConfig = configReference.getPotentialAutoCloseable(); if (config.equals(existingConfig)) { try { entryIterator.remove(); } catch (final RuntimeException runtimeException) { if (throwMe == null) { throwMe = runtimeException; } else { throwMe.addSuppressed(runtimeException); } } if (existingConfig instanceof AutoCloseable) { try { ((AutoCloseable)existingConfig).close(); } catch (final RuntimeException runtimeException) { if (throwMe == null) { throwMe = runtimeException; } else { throwMe.addSuppressed(runtimeException); } } catch (final InterruptedException interruptedException) { Thread.currentThread().interrupt(); } catch (final Exception exception) { if (throwMe == null) { throwMe = new RuntimeException(exception.getMessage(), exception); } else { throwMe.addSuppressed(exception); } } } } } } } } } } } if (throwMe != null) { throw throwMe; } } } @SuppressWarnings("try") private static final class WeakAutoCloseableReference extends WeakReference implements AutoCloseable { private final A potentialAutoCloseable; private WeakAutoCloseableReference(final R referent, final A potentialAutoCloseable, final ReferenceQueue referenceQueue) { super(referent, Objects.requireNonNull(referenceQueue)); this.potentialAutoCloseable = potentialAutoCloseable; } private final A getPotentialAutoCloseable() { return this.potentialAutoCloseable; } @Override public final void close() throws Exception { final A potentialAutoCloseable = this.getPotentialAutoCloseable(); if (potentialAutoCloseable instanceof AutoCloseable) { ((AutoCloseable)potentialAutoCloseable).close(); } } } private static final class AutoCloseableCloserThread extends Thread { private final ReferenceQueue referenceQueue; private AutoCloseableCloserThread(final ReferenceQueue referenceQueue) { super(AutoCloseableCloserThread.class.getName()); this.setDaemon(true); this.setPriority(Thread.MIN_PRIORITY + 1); // low but not too low this.referenceQueue = Objects.requireNonNull(referenceQueue); } @Override public final void run() { while (!this.isInterrupted()) { try { final Object reference = this.referenceQueue.remove(); if (reference instanceof AutoCloseable) { try { ((AutoCloseable)reference).close(); } catch (final InterruptedException interruptedException) { this.interrupt(); } catch (final RuntimeException throwMe) { throw throwMe; } catch (final Exception exception) { throw new RuntimeException(exception.getMessage(), exception); } } } catch (final InterruptedException interruptedException) { this.interrupt(); } catch (final RuntimeException throwMe) { throw throwMe; } catch (final Exception exception) { throw new RuntimeException(exception.getMessage(), exception); } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy