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.rest.server.springboot;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.util.*;
023import java.util.concurrent.atomic.*;
024import java.util.function.*;
025
026import org.apache.juneau.commons.inject.*;
027import org.apache.juneau.commons.settings.*;
028import org.springframework.context.*;
029
030/**
031 * A bean store that uses Spring bean resolution to find beans if they're not already in this store.
032 *
033 * <p>
034 * This implementation extends {@link BasicBeanStore} and delegates to Spring's {@link ApplicationContext}
035 * for bean resolution when beans are not found in the local store or parent stores.
036 *
037 * <h5 class='section'>Example:</h5>
038 * <p class='bjava'>
039 *    <jc>// Create a Spring bean store</jc>
040 *    ApplicationContext <jv>appContext</jv> = ...;
041 *    SpringBeanStore <jv>beanStore</jv> = <jk>new</jk> SpringBeanStore(<jv>appContext</jv>, <jk>null</jk>);
042 *
043 *    <jc>// Get bean from Spring context</jc>
044 *    Optional&lt;MyService&gt; <jv>service</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>);
045 *
046 *    <jc>// Get named bean from Spring context</jc>
047 *    Optional&lt;MyService&gt; <jv>namedService</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>, <js>"primary"</js>);
048 *
049 *    <jc>// Get all beans of a type from Spring context</jc>
050 *    Map&lt;String, MyService&gt; <jv>allServices</jv> = <jv>beanStore</jv>.getBeansOfType(MyService.<jk>class</jk>);
051 * </p>
052 *
053 * <h5 class='section'>See Also:</h5><ul>
054 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestServerSpringboot">juneau-rest-server-springboot</a>
055 *    <li class='jc'>{@link BasicBeanStore}
056 *    <li class='jic'>{@link BeanStore}
057 * </ul>
058 */
059public class SpringBeanStore extends BasicBeanStore {
060
061   private final ApplicationContext appContext;
062
063   // The shared Environment bridge for appContext (null when appContext is null).  One per context,
064   // reference-counted across stores; see SpringEnvironmentBridges.
065   private final SpringEnvironmentBridges.Registration registration;
066
067   // clear() may be called repeatedly; this store releases its reference at most once.
068   private final AtomicBoolean released = new AtomicBoolean();
069
070   /**
071    * Constructor.
072    *
073    * <p>
074    * If {@code appContext} is non-null, this constructor also makes sure the process-wide {@link Settings} singleton
075    * holds a {@link SpringEnvironmentPropertySource} for it, so that {@link Value @Value("&#123;...&#125;")}
076    * placeholders resolve against the Spring {@link org.springframework.core.env.Environment Environment} (i.e. against
077    * {@code application.yaml}, env vars, system properties, etc.).  There is one such source per application context,
078    * shared by every store built on it and kept at the position of the first registration.  It is removed when every
079    * store on the context has been {@link #clear() cleared}, or when the context closes (for a
080    * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}).
081    *
082    * @param appContext The Spring application context used to resolve beans. Can be <jk>null</jk> (no Spring environment bridge is installed).
083    * @param parent The parent bean store. Can be <jk>null</jk> for a root store with no parent chain.
084    */
085   public SpringBeanStore(ApplicationContext appContext, BeanStore parent) {
086      super(parent);
087      this.appContext = appContext;
088      // The bridge resolves appContext.getEnvironment() lazily on the first @Value("${...}") lookup, so the
089      // constructor has no observable interaction with the application context.  Null appContext means no bridge.
090      this.registration = nn(appContext) ? SpringEnvironmentBridges.acquire(appContext) : null;
091   }
092
093   @Override
094   @SuppressWarnings({
095      "resource" // super.clear() returns this; the discarded return is the store we already own
096   })
097   public SpringBeanStore clear() {
098      try {
099         super.clear();
100      } finally {
101         // Release even when super.clear() throws (it does once this store is closed), so the reference cannot leak.
102         // Defensive: registration is null until this constructor finishes (BasicBeanStore's constructors do not call clear() today).
103         if (nn(registration) && released.compareAndSet(false, true))
104            SpringEnvironmentBridges.release(registration);
105      }
106      return this;
107   }
108
109   /**
110    * Returns the shared Environment bridge this store acquired.
111    *
112    * @return The bridge, or <jk>null</jk> if this store has no application context.
113    */
114   SpringEnvironmentPropertySource environmentSource() {
115      return registration == null ? null : registration.source;
116   }
117
118   /**
119    * Clears the miss cache of the Spring {@code Environment} bridge registered for the given application context.
120    *
121    * <p>
122    * Needed only after a Spring property source's contents change in place for a key that is not a valid Spring Boot
123    * property name and that Juneau already looked up and missed; see {@link SpringEnvironmentPropertySource#invalidate()}.
124    * The bridge is shared by every {@code SpringBeanStore} on the context, so this affects all of them.
125    *
126    * @param appContext The application context.  Can be <jk>null</jk>.
127    * @return <jk>true</jk> if the context had a registered bridge.
128    * @since 10.0.0
129    */
130   public static boolean invalidateEnvironmentCache(ApplicationContext appContext) {
131      return SpringEnvironmentBridges.invalidate(appContext);
132   }
133
134   @Override
135   public <T> Optional<T> getBean(Class<T> beanType) {
136      // Try local store first
137      var o = super.getBean(beanType);
138      if (o.isPresent())
139         return o;
140
141      // Fall back to Spring context
142      if (nn(appContext)) {
143         var bean = safeOpt(() -> appContext.getBeanProvider(beanType).getIfAvailable()).orElse(null);
144         return bean != null ? o(bean) : oe();
145      }
146
147      return oe();
148   }
149
150   @Override
151   public <T> Optional<T> getBean(Class<T> beanType, String name) {
152      // Try local store first
153      var o = super.getBean(beanType, name);
154      if (o.isPresent())
155         return o;
156
157      // Fall back to Spring context
158      if (nn(appContext) && nn(name) && safeOpt(() -> appContext.containsBean(name)).orElse(false)) {
159         var bean = safeOpt(() -> appContext.getBean(name, beanType)).orElse(null);
160         return bean != null ? o(bean) : oe();
161      }
162
163      return oe();
164   }
165
166   @Override
167   public <T> Map<String,T> getBeansOfType(Class<T> beanType) {
168      // Get beans from parent stores and local store
169      Map<String,T> result = super.getBeansOfType(beanType);
170
171      // Add beans from Spring context (if they don't already exist in the result)
172      if (nn(appContext)) {
173         safeOpt(() -> appContext.getBeansOfType(beanType)).ifPresent(springBeans -> springBeans.forEach(result::putIfAbsent));
174      }
175
176      return result;
177   }
178
179   @Override
180   public boolean hasBean(Class<?> beanType) {
181      // Check local store and parents first
182      if (super.hasBean(beanType))
183         return true;
184
185      // Check Spring context
186      if (nn(appContext)) {
187         return safeOpt(() -> appContext.getBeanNamesForType(beanType))
188            .map(beanNames -> beanNames.length > 0)
189            .orElse(false);
190      }
191
192      return false;
193   }
194
195   @Override
196   public boolean hasBean(Class<?> beanType, String name) {
197      // Check local store and parents first
198      if (super.hasBean(beanType, name))
199         return true;
200
201      // Check Spring context
202      if (nn(appContext) && nn(name)) {
203         return safeOpt(() -> appContext.containsBean(name) && appContext.isTypeMatch(name, beanType))
204            .orElse(false);
205      }
206
207      return false;
208   }
209
210   // No getBeanSupplier(Class) override: BasicBeanStore.getBeanSupplier(Class) delegates via unqualified
211   // this.getBeanSupplier(beanType, null), which polymorphically dispatches to this class's own
212   // getBeanSupplier(Class,String) override below — its name==null branch already performs the Spring lookup.
213
214   @Override
215   public <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType, String name) {
216      // Try local store and parents first
217      var supplier = super.getBeanSupplier(beanType, name);
218      if (supplier.isPresent())
219         return supplier;
220
221      // Unnamed lookup: also consult Spring by type so beans like Environment are reachable when this
222      // SpringBeanStore is installed in the BasicBeanStore.overridingParent slot (which always passes
223      // name=null through the resolution chain).
224      if (name == null && nn(appContext)) {
225         var providerOpt = safeOpt(() -> appContext.getBeanProvider(beanType));
226         if (providerOpt.isPresent()) {
227            var provider = providerOpt.get();
228            if (provider.getIfAvailable() != null)
229               return o(provider::getIfAvailable);
230         }
231         return oe();
232      }
233
234      // Return a supplier that delegates to Spring context
235      if (nn(appContext) && safeOpt(() -> appContext.containsBean(name) && appContext.isTypeMatch(name, beanType)).orElse(false)) {
236         return o(() -> safeOpt(() -> appContext.getBean(name, beanType)).orElse(null));
237      }
238
239      return oe();
240   }
241}
242