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<MyService> <jv>service</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>); 045 * 046 * <jc>// Get named bean from Spring context</jc> 047 * Optional<MyService> <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<String, MyService> <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("{...}")} 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