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.commons.inject; 018 019import static java.util.stream.Collectors.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022 023import java.util.*; 024import java.util.function.*; 025 026import org.apache.juneau.commons.reflect.*; 027 028/** 029 * Spring-bean-like interface for looking up beans by type and name. 030 * 031 * <p> 032 * This interface provides a minimal contract for bean lookup operations, similar to Spring's 033 * <a class="doclink" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html">BeanFactory</a> interface. 034 * 035 * <p> 036 * Implementations of this interface can be backed by various sources: 037 * <ul> 038 * <li>Spring's <a class="doclink" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html">ApplicationContext</a> 039 * <li>Custom in-memory stores 040 * <li>Other dependency injection frameworks 041 * </ul> 042 * 043 * <h5 class='section'>Example:</h5> 044 * <p class='bjava'> 045 * BeanStore <jv>beanStore</jv> = ...; 046 * 047 * <jc>// Get bean by type</jc> 048 * Optional<MyService> <jv>service</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>); 049 * 050 * <jc>// Get bean by name</jc> 051 * Optional<Object> <jv>bean</jv> = <jv>beanStore</jv>.getBean(<js>"myBean"</js>); 052 * 053 * <jc>// Get bean by type and name</jc> 054 * Optional<MyService> <jv>namedService</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>, <js>"primary"</js>); 055 * 056 * <jc>// Get all beans of a type</jc> 057 * Map<String, MyService> <jv>allServices</jv> = <jv>beanStore</jv>.getBeansOfType(MyService.<jk>class</jk>); 058 * </p> 059 * 060 * <h5 class='section'>See Also:</h5><ul> 061 * <li class='link'><a class="doclink" href="https://docs.spring.io/spring-framework/reference/core/beans.html">Spring Framework - The IoC Container</a> 062 * </ul> 063 */ 064public interface BeanStore { 065 066 /** 067 * Returns a bean of the specified type. 068 * 069 * <p> 070 * If multiple beans of the specified type exist, the behavior is implementation-dependent. 071 * Typically, implementations will return the primary bean or throw an exception. 072 * 073 * @param <T> The bean type. 074 * @param beanType The bean type. 075 * @return The bean, or {@link Optional#empty()} if no bean of the specified type exists. 076 */ 077 <T> Optional<T> getBean(Class<T> beanType); 078 079 /** 080 * Returns a bean of the specified type and name. 081 * 082 * @param <T> The bean type. 083 * @param beanType The bean type. 084 * @param name The bean name. Can be <jk>null</jk> for unnamed beans. 085 * @return The bean, or {@link Optional#empty()} if no bean of the specified type and name exists. 086 */ 087 <T> Optional<T> getBean(Class<T> beanType, String name); 088 089 /** 090 * Returns all beans of the specified type, keyed by bean name. 091 * 092 * <p> 093 * The map keys are the bean names, and the values are the bean instances. 094 * If no beans of the specified type exist, returns an empty map. 095 * 096 * @param <T> The bean type. 097 * @param beanType The bean type. 098 * @return A map of bean names to bean instances. Never <jk>null</jk>. 099 */ 100 <T> Map<String,T> getBeansOfType(Class<T> beanType); 101 102 /** 103 * Returns <jk>true</jk> if this store contains at least one bean of the specified type. 104 * 105 * @param beanType The bean type. 106 * @return <jk>true</jk> if this store contains at least one bean of the specified type. 107 */ 108 boolean hasBean(Class<?> beanType); 109 110 /** 111 * Returns <jk>true</jk> if this store contains a bean of the specified type and name. 112 * 113 * @param beanType The bean type. 114 * @param name The bean name. Can be <jk>null</jk> for unnamed beans. 115 * @return <jk>true</jk> if this store contains a bean of the specified type and name. 116 */ 117 boolean hasBean(Class<?> beanType, String name); 118 119 /** 120 * Convenience shortcut for instantiating a bean of the specified type using this store. 121 * 122 * <p> 123 * Equivalent to: 124 * <p class='bjava'> 125 * BeanInstantiator.<jsm>of</jsm>(<jv>type</jv>, <jk>this</jk>).run() 126 * </p> 127 * 128 * <p> 129 * Use this when you don't need any of {@link BeanInstantiator}'s configuration options 130 * (subtype, factory-method names, builder hooks, etc.). For more control over instantiation, 131 * use {@link BeanInstantiator#of(Class, BeanStore)} directly. 132 * 133 * <h5 class='section'>Example:</h5> 134 * <p class='bjava'> 135 * <jc>// Instantiate a bean using this store for parameter resolution</jc> 136 * MyService <jv>service</jv> = <jv>beanStore</jv>.instantiate(MyService.<jk>class</jk>); 137 * </p> 138 * 139 * @param <T> The bean type. 140 * @param type The bean type to instantiate. Must not be <jk>null</jk>. 141 * @return The instantiated bean. 142 */ 143 default <T> T instantiate(Class<T> type) { 144 return BeanInstantiator.of(type, this).run(); 145 } 146 147 /** 148 * Returns the supplier for an unnamed bean of the specified type. 149 * 150 * <p> 151 * If no supplier is found in this store, searches the parent store recursively. 152 * 153 * @param <T> The bean type. 154 * @param beanType The bean type. 155 * @return The supplier, or {@link Optional#empty()} if no supplier of the specified type exists. 156 */ 157 <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType); 158 159 /** 160 * Returns the supplier for a named bean of the specified type. 161 * 162 * <p> 163 * If no supplier with the specified name is found in this store, searches the parent store recursively. 164 * 165 * @param <T> The bean type. 166 * @param beanType The bean type. 167 * @param name The bean name. Can be <jk>null</jk> for unnamed beans. 168 * @return The supplier, or {@link Optional#empty()} if no supplier of the specified type and name exists. 169 */ 170 <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType, String name); 171 172 /** 173 * Finds and invokes a factory method that produces a bean of type <c>beanType</c>. 174 * 175 * <p> 176 * Scans the methods of the resource class identified by <c>onClassOrObject</c>: 177 * <ul> 178 * <li>If <c>onClassOrObject</c> is a {@link Class}, only <jk>static</jk> methods are eligible and 179 * the instance parameter passed to the invoked method is <jk>null</jk>. 180 * <li>If <c>onClassOrObject</c> is any other object, both instance and static methods on its class are 181 * eligible, and the object itself is passed as the receiver. 182 * </ul> 183 * 184 * <p> 185 * The search has two passes. The first pass considers public methods only. If it finds nothing, a second pass 186 * considers <em>non-public</em> methods (protected, package-private or private) across the class hierarchy, 187 * child class first, but only those that carry {@link Bean @Bean} (declared directly or inherited from a method 188 * they override; see {@link BeanAnnotation#find(MethodInfo)}). A non-public method without {@link Bean @Bean} is 189 * never invoked, even when <c>filter</c> is <jk>null</jk>. Non-public methods are made accessible before invocation. 190 * 191 * <p> 192 * The first method satisfying all of the following is invoked: 193 * <ol> 194 * <li>Not deprecated. 195 * <li>Return type is assignment-compatible with <c>beanType</c>. 196 * <li>Accepted by <c>filter</c> (if non-<jk>null</jk>; otherwise any method qualifies). 197 * <li>All parameters can be resolved from this store plus any <c>extraBeans</c>. 198 * </ol> 199 * 200 * <p> 201 * The default implementation returns {@link Optional#empty()}. 202 * Override in concrete stores (see {@link BasicBeanStore}) to enable factory-method scanning. 203 * 204 * <h5 class='section'>Example:</h5> 205 * <p class='bjava'> 206 * <jc>// Filter only</jc> 207 * <jv>beanStore</jv>.createBeanFromMethod(RichLogger.<jk>class</jk>, <jv>resource</jv>, 208 * <jv>m</jv> -> <jv>m</jv>.hasAnnotation(Bean.<jk>class</jk>)) 209 * .ifPresent(<jv>creator</jv>::impl); 210 * 211 * <jc>// Filter + extra bean not yet in the store</jc> 212 * <jv>beanStore</jv>.createBeanFromMethod(EncoderSet.<jk>class</jk>, <jv>resource</jv>, 213 * <jv>m</jv> -> <jv>m</jv>.hasAnnotation(Bean.<jk>class</jk>), <jv>builder</jv>) 214 * .ifPresent(<jv>x</jv> -> <jv>builder</jv>.impl(<jv>x</jv>)); 215 * 216 * <jc>// No filter, no extra beans</jc> 217 * <jv>beanStore</jv>.createBeanFromMethod(RichLogger.<jk>class</jk>, <jv>resource</jv>); 218 * </p> 219 * 220 * @param <T> The bean type. 221 * @param beanType The type of bean to create. Must not be <jk>null</jk>. 222 * @param onClassOrObject The object instance or {@link Class} whose methods are searched. 223 * Must not be <jk>null</jk>. 224 * @param filter Optional predicate restricting which methods are eligible. Can be <jk>null</jk> (any method qualifies). 225 * @param extraBeans Optional bean instances visible to parameter resolution for this call only. 226 * These are <em>not</em> registered in the store. 227 * @return The created bean wrapped in an {@link Optional}, or {@link Optional#empty()} if no matching 228 * factory method was found. 229 * @throws BeanCreationException If a matching method was found but threw an exception during invocation. 230 * @see BeanInstantiator BeanInstantiator — for instantiating a bean from its own constructors, builders, or factory methods 231 */ 232 default <T> Optional<T> createBeanFromMethod(Class<T> beanType, Object onClassOrObject, Predicate<MethodInfo> filter, Object... extraBeans) { 233 return oe(); 234 } 235 236 /** 237 * Invokes one specific factory method to produce a bean of type <c>beanType</c>. 238 * 239 * <p> 240 * Unlike {@link #createBeanFromMethod(Class, Object, Predicate, Object...)}, no search is performed: 241 * the caller supplies the {@link MethodInfo} it has already chosen. Visibility is not a gate: 242 * public, protected, package-private and private methods are all invoked (non-public methods are made accessible first). 243 * Deprecation is not checked. 244 * 245 * <p> 246 * If <c>onClassOrObject</c> is a {@link Class}, only <jk>static</jk> methods can be invoked. Otherwise the object 247 * is used as the receiver for instance methods. 248 * 249 * <p> 250 * Returns {@link Optional#empty()} when the method is not eligible: it returns <jk>void</jk>, it is a bridge or 251 * synthetic method, it is an instance method and <c>onClassOrObject</c> is a {@link Class}, or one of its parameters 252 * cannot be resolved from this store plus <c>extraBeans</c>. 253 * 254 * <h5 class='section'>Example:</h5> 255 * <p class='bjava'> 256 * <jc>// Invoke one specific @Bean factory method, instead of searching by return type.</jc> 257 * MethodInfo <jv>m</jv> = ClassInfo.<jsm>of</jsm>(MyResource.<jk>class</jk>).getDeclaredMethod(<jv>x</jv> -> <jv>x</jv>.hasName(<js>"cacheIndicator"</js>)).get(); 258 * Optional<HealthIndicator> <jv>cache</jv> = <jv>beanStore</jv>.invokeBeanMethod(HealthIndicator.<jk>class</jk>, <jv>m</jv>, <jv>resource</jv>); 259 * </p> 260 * 261 * @param <T> The bean type. 262 * @param beanType The expected bean type. Must be assignable from the method's return type. Must not be <jk>null</jk>. 263 * @param method The factory method to invoke. Must not be <jk>null</jk>. 264 * @param onClassOrObject The receiver object, or the {@link Class} for static methods. Must not be <jk>null</jk>. 265 * The method's declaring class must be assignable from this object's class (or this class). 266 * @param extraBeans Optional bean instances visible to parameter resolution for this call only. 267 * @return The created bean, or {@link Optional#empty()} if the method is not eligible. 268 * @throws IllegalArgumentException If an argument is <jk>null</jk>, <c>beanType</c> is not assignable from the 269 * method's return type, or the method does not belong to the target's class hierarchy. 270 * @throws BeanCreationException If the method cannot be made accessible or threw an exception during invocation. 271 */ 272 default <T> Optional<T> invokeBeanMethod(Class<T> beanType, MethodInfo method, Object onClassOrObject, Object... extraBeans) { 273 reqnn("beanType", beanType); 274 reqnn("method", method); 275 reqnn("onClassOrObject", onClassOrObject); 276 var returnType = method.getReturnType(); 277 if (returnType.is(void.class) || method.isBridge() || method.isSynthetic()) 278 return Optional.empty(); 279 req(ClassInfo.of(beanType).getWrapperIfPrimitive().inner().isAssignableFrom(returnType.getWrapperIfPrimitive().inner()), 280 "Bean type [%s] is not assignable from the return type [%s] of method [%s].", beanType.getSimpleName(), returnType.getNameSimple(), method.getLabel()); 281 var isClass = onClassOrObject instanceof Class; 282 Class<?> target = isClass ? (Class<?>)onClassOrObject : onClassOrObject.getClass(); 283 req(method.inner().getDeclaringClass().isAssignableFrom(target), 284 "Method [%s] is not declared on the hierarchy of [%s].", method.getLabel(), target.getSimpleName()); 285 if (method.isNotStatic() && isClass) 286 return Optional.empty(); 287 if (! method.canResolveAllParameters(this, extraBeans)) 288 return Optional.empty(); 289 return Optional.ofNullable(BeanMethodInvoker.invoke(this, beanType, method, method.isStatic() ? null : onClassOrObject, extraBeans)); 290 } 291 292 /** 293 * Convenience overload of {@link #createBeanFromMethod(Class, Object, Predicate, Object...)} with no filter and no extra beans. 294 * 295 * @param <T> The bean type. 296 * @param beanType The type of bean to create. Must not be <jk>null</jk>. 297 * @param onClassOrObject The object instance or {@link Class} whose methods are searched. 298 * Must not be <jk>null</jk>. 299 * @return The created bean wrapped in an {@link Optional}, or {@link Optional#empty()} if no matching 300 * factory method was found. 301 * @throws BeanCreationException If a matching method was found but threw an exception during invocation. 302 */ 303 default <T> Optional<T> createBeanFromMethod(Class<T> beanType, Object onClassOrObject) { 304 return createBeanFromMethod(beanType, onClassOrObject, null); 305 } 306 307 /** 308 * Returns the implementation class registered for the specified bean type, if any. 309 * 310 * <p> 311 * Type bindings let callers register a default implementation class to be instantiated when an 312 * actual bean of <c>beanType</c> is needed. Bindings are inherited through the parent chain. 313 * 314 * <p> 315 * The default implementation returns {@link Optional#empty()} (no binding). Override in concrete 316 * stores (see {@link BasicBeanStore}) to enable type binding. 317 * 318 * @param <T> The bean type. 319 * @param beanType The bean type whose implementation class is being looked up. 320 * @return The implementation class, or {@link Optional#empty()} if no binding exists. 321 */ 322 default <T> Optional<Class<? extends T>> getBeanType(Class<T> beanType) { 323 return oe(); 324 } 325 326 /** 327 * Returns <jk>true</jk> if this store can satisfy every parameter of the specified executable. 328 * 329 * <p> 330 * Skips: 331 * <ul> 332 * <li>The first parameter when it is an instance of <c>outer</c> (inner-class outer reference). 333 * <li>Parameters typed as {@link Optional} (always considered satisfied). 334 * </ul> 335 * 336 * @param executable The constructor or method to inspect. Must not be <jk>null</jk>. 337 * @param outer The outer object to use for inner-class param resolution. Can be <jk>null</jk> (the outer-instance skip rule is then never applied). 338 * @return <jk>true</jk> if every required parameter can be resolved from this store. 339 */ 340 default boolean hasAllParams(ExecutableInfo executable, Object outer) { 341 for (var i = 0; i < executable.getParameterCount(); i++) { 342 var pi = executable.getParameter(i); 343 var pt = pi.getParameterType(); 344 if ((i == 0 && nn(outer) && pt.isInstance(outer)) || pt.is(Optional.class)) 345 continue; 346 var beanQualifier = pi.getResolvedQualifier(); 347 var ptc = pt.inner(); 348 if ((beanQualifier == null && ! hasBean(ptc)) || (nn(beanQualifier) && ! hasBean(ptc, beanQualifier))) 349 return false; 350 } 351 return true; 352 } 353 354 /** 355 * Resolves every parameter of the specified executable from this store. 356 * 357 * <p> 358 * For {@link Optional}-typed parameters the wrapped {@link Optional} is returned directly; for all 359 * others the bean instance (or <jk>null</jk> when missing) is returned. See {@link #hasAllParams} 360 * for the full skip rules. 361 * 362 * @param executable The constructor or method to resolve parameters for. Must not be <jk>null</jk>. 363 * @param outer The outer object to use for inner-class param resolution. Can be <jk>null</jk> (the outer-instance skip rule is then never applied). 364 * @return An array of resolved parameter values, one per parameter of <c>executable</c>. 365 */ 366 default Object[] getParams(ExecutableInfo executable, Object outer) { 367 var o = new Object[executable.getParameterCount()]; 368 for (var i = 0; i < executable.getParameterCount(); i++) { 369 var pi = executable.getParameter(i); 370 var pt = pi.getParameterType(); 371 if (i == 0 && nn(outer) && pt.isInstance(outer)) { 372 o[i] = outer; 373 } else { 374 var beanQualifier = pi.getResolvedQualifier(); 375 var ptc = pt.unwrap(Optional.class).inner(); 376 var o2 = beanQualifier == null ? getBean(ptc) : getBean(ptc, beanQualifier); 377 o[i] = pt.is(Optional.class) ? o2 : o2.orElse(null); 378 } 379 } 380 return o; 381 } 382 383 /** 384 * Returns a comma-delimited list of parameter type names that cannot be resolved from this store. 385 * 386 * <p> 387 * Intended for diagnostic error messages — see {@link #hasAllParams} for the exact match rules. 388 * Skipped parameters (outer-instance match, {@link Optional}) never appear in the result. 389 * 390 * @param executable The constructor or method to inspect. Must not be <jk>null</jk>. 391 * @param outer The outer object to use for inner-class param resolution. Can be <jk>null</jk> (the outer-instance skip rule is then never applied). 392 * @return A comma-delimited list of missing parameter type names, or <jk>null</jk> when none are missing. 393 */ 394 default String getMissingParams(ExecutableInfo executable, Object outer) { 395 var params = executable.getParameters(); 396 List<String> l = list(); 397 for (var i = 0; i < params.size(); i++) { 398 var pi = params.get(i); 399 var pt = pi.getParameterType(); 400 if ((i == 0 && nn(outer) && pt.isInstance(outer)) || pt.is(Optional.class)) 401 continue; 402 var beanName = pi.getResolvedQualifier(); 403 var ptc = pt.inner(); 404 if (beanName == null && ! hasBean(ptc)) 405 l.add(pt.getNameSimple()); 406 if (nn(beanName) && ! hasBean(ptc, beanName)) 407 l.add(pt.getNameSimple() + '@' + beanName); 408 } 409 return l.isEmpty() ? null : l.stream().sorted().collect(joining(",")); 410 } 411} 412