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.config;
018
019import static org.apache.juneau.commons.utils.ObjectUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import org.apache.juneau.commons.settings.*;
023
024/**
025 * Auto-registers a classpath-default {@code juneau.cfg} (and the other names returned by
026 * {@link Config#getCandidateSystemDefaultConfigNames()}) as a {@link PropertySource} for the
027 * process-wide {@link Settings} singleton.
028 *
029 * <p>
030 * Discovered by {@link org.apache.juneau.commons.settings.Settings.Builder#useServiceLoader() ServiceLoader-based wiring} through a
031 * {@code META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider} entry
032 * shipped in this module.
033 *
034 * <p>
035 * Lookup semantics: the registered source defers calling {@link Config#getSystemDefault()} to the
036 * first {@code get(name)} invocation. This is deliberate — calling {@code getSystemDefault()} from
037 * inside {@link #create()} would create a static-initialization cycle because
038 * {@code Settings.<clinit>} fires the ServiceLoader at class-load time, and {@code Config}'s
039 * static initializers transitively reference {@link Settings} (through {@code Cache} / {@code SystemUtils.env(...)})
040 * which would re-enter the {@code Settings.<clinit>} block.
041 *
042 * <p>
043 * Lazy resolution makes the cycle a non-issue: by the time {@code @Value("${...}")} fires its first
044 * lookup, all participating classes are fully initialized.
045 *
046 * <h5 class='section'>Silence on missing config:</h5>
047 * <p>
048 * If no system-default {@link Config} is discoverable (no {@code juneau.cfg} / {@code default.cfg}
049 * / {@code application.cfg} / etc. on the classpath or working directory), {@code get(name)}
050 * returns {@link PropertyLookupResult#missing()} for every key.  No logging, no exception — many
051 * deployments have no classpath config and shouldn't see noise.
052 *
053 * <h5 class='section'>Ordering:</h5>
054 * <p>
055 * {@link #order()} returns {@code 100}, which is "low" in the {@link PropertySourceProvider}
056 * scheme — provider-discovered classpath defaults register near the bottom of the source list so
057 * that programmatic {@code Settings.get().addSource(...)} calls (e.g. per-microservice config or
058 * the Spring {@code Environment} bridge) shadow the classpath default.  {@link Settings} walks
059 * sources in reverse insertion order, so the most-recently-added source wins.
060 */
061public class ConfigPropertySourceProvider implements PropertySourceProvider {
062
063   @Override
064   public PropertySource create() {
065      // Return a lazy wrapper.  Resolving Config.getSystemDefault() eagerly here would trigger
066      // Config.<clinit>, which can transitively re-enter Settings.<clinit> through Cache /
067      // SystemUtils.env(...) — the SPI registration is invoked from inside the Settings static init
068      // block, so any cycle here surfaces as ExceptionInInitializerError.
069      return new LazyConfigPropertySource();
070   }
071
072   @Override
073   public int order() {
074      // Low ordering: classpath defaults register first, so any programmatic
075      // addSource(...) added afterwards shadows the classpath defaults at lookup time
076      // (Settings walks sources in reverse insertion order).
077      return 100;
078   }
079
080   /**
081    * Defers {@link Config#getSystemDefault()} resolution to the first {@link #get(String)} call to
082    * avoid the static-initialization cycle described in the enclosing class' Javadoc.
083    *
084    * <p>
085    * Additionally protects against three runtime cycles at first-use time:
086    * <ol>
087    *    <li><b>Bootstrap-static-init cycle (poisoning).</b> If this source is called from inside
088    *       <i>any</i> JVM static-initializer chain that transitively loads {@code Config}, the
089    *       downstream {@code Config.findSystemDefault()} → {@code FileStore.<clinit>} →
090    *       {@code Context.Builder.build()} chain can fail with {@link NullPointerException}
091    *       because {@code Context.<clinit>} hasn't finished yet. That failure <i>poisons</i>
092    *       {@code FileStore} (every subsequent reference raises {@link NoClassDefFoundError}).
093    *       To prevent the poisoning, the source detects when it's called from a static-initializer
094    *       frame by walking the current stack for a {@code "<clinit>"} method name and short-circuits
095    *       to {@link PropertyLookupResult#missing()} <i>without</i> memoizing — so the next call,
096    *       from regular code, can resolve normally.
097    *    <li><b>Reentrant resolution cycle.</b> {@link Config#findSystemDefault()} looks up the
098    *       {@code "juneau.configFile"} system property via {@link Settings#get(String)}, which
099    *       walks every registered source — including this one. Without a guard, that re-enters
100    *       {@code Config.getSystemDefault()} on the same thread, which re-enters
101    *       {@code findSystemDefault()}, which re-enters {@code Settings.get("juneau.configFile")},
102    *       ad infinitum (a {@link StackOverflowError}). A {@link ThreadLocal} sentinel short-circuits
103    *       any re-entrant lookup on the same thread to {@code missing()}.
104    *    <li><b>Defensive throwable catch.</b> Any unexpected throwable (e.g. a different class
105    *       loader's {@code Config} init failure that the {@code <clinit>}-sniff missed) is
106    *       swallowed and treated as "not yet available" — {@code get(name)} returns
107    *       {@link PropertyLookupResult#missing()} without memoizing the failure.
108    * </ol>
109    */
110   @SuppressWarnings({
111      "java:S1181" // Intentional broad Throwable catch — see Javadoc.
112   })
113   static final class LazyConfigPropertySource implements PropertySource {
114
115      // Thread-local re-entrance guard. Static so it covers both the lazy-init path and any
116      // concurrent lookup that may also reach a Config.getSystemDefault() call (defensively).
117      private static final ThreadLocal<Boolean> RESOLVING = ThreadLocal.withInitial(() -> Boolean.FALSE);
118
119      // Guarded by the synchronized block in get(...); volatile reads cover the fast-path check.
120      private volatile boolean resolved;
121      @SuppressWarnings({
122         "java:S3077" // Publish-once reference: delegate is fully constructed before its single assignment inside the synchronized block, so volatile visibility is sufficient.
123      })
124      private volatile ConfigPropertySource delegate;
125
126      @Override
127      @SuppressWarnings({
128         "java:S3776" // Cognitive complexity acceptable for lazy config property source resolution
129      })
130      public PropertyLookupResult get(String name) {
131
132         // Re-entrant guard: Config.findSystemDefault() walks Settings.get("juneau.configFile"),
133         // which round-trips through every source including this one. Short-circuit on
134         // re-entry so the outer Config.getSystemDefault() can make progress.
135         if (isTrue(RESOLVING.get()))
136            return PropertyLookupResult.missing();
137
138         // Bootstrap guard: if we're being called from inside a <clinit> frame (e.g. another
139         // class's static-initializer chain transitively invokes Settings.get(...) → this
140         // source), don't attempt to resolve Config — doing so would force Config / FileStore /
141         // Context class loading from inside an already-in-progress class init, which poisons
142         // FileStore.<clinit> with a NoClassDefFoundError that survives the rest of the JVM
143         // lifetime. Short-circuit without memoizing; the next non-<clinit> call resolves.
144         if (! resolved && isInClinit())
145            return PropertyLookupResult.missing();
146
147         if (! resolved) {
148            synchronized (this) {
149               if (! resolved) {
150                  // Re-check the <clinit> guard inside the lock — another thread may have
151                  // flipped resolved between the first check and the lock acquire.
152                  if (isInClinit())
153                     return PropertyLookupResult.missing();
154
155                  Config cfg;
156                  RESOLVING.set(Boolean.TRUE);
157                  try {
158                     cfg = Config.getSystemDefault();
159                  } catch (@SuppressWarnings("unused") Throwable t) {
160                     // Defense-in-depth: a Config-side init failure the <clinit> sniff
161                     // missed. Return missing() without memoizing so the next lookup can
162                     // retry once Config init has completed.
163                     return PropertyLookupResult.missing();
164                  } finally {
165                     RESOLVING.remove();
166                  }
167                  // Silent when no juneau.cfg / default.cfg / etc. is discoverable — the
168                  // SPI consumer sees missing() for every key but no log noise.
169                  delegate = (cfg == null) ? null : new ConfigPropertySource(cfg);
170                  resolved = true;
171               }
172            }
173         }
174         return delegate == null ? PropertyLookupResult.missing() : delegate.get(name);
175      }
176
177      /**
178       * Returns <jk>true</jk> if any frame in the current thread's call stack is a Java
179       * static-initializer ({@code "<clinit>"}). Used to detect — and avoid — the bootstrap-time
180       * cycle that would otherwise poison {@code FileStore.<clinit>}.
181       *
182       * <p>
183       * Cost: a single {@link Thread#getStackTrace()} call on the first lookup per source-thread
184       * pair. {@code resolved} is volatile so subsequent calls skip the stack walk entirely.
185       *
186       * @return <jk>true</jk> if the current call is being made from inside a {@code <clinit>}
187       *    frame anywhere in the stack.
188       */
189      private static boolean isInClinit() {
190         var trace = Thread.currentThread().getStackTrace();
191         for (var frame : trace) {
192            if (eq(frame.getMethodName(), "<clinit>"))
193               return true;
194         }
195         return false;
196      }
197   }
198}