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 org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024import java.util.concurrent.*; 025import java.util.function.*; 026 027import org.apache.juneau.commons.reflect.*; 028import org.apache.juneau.commons.settings.*; 029import org.apache.juneau.commons.svl.*; 030 031/** 032 * Internal helper used by the reflection layer to resolve {@code @Value}-annotated injection sites. 033 * 034 * <p> 035 * Splits the {@code @Value} handling into three small pieces so callers (field / parameter / method) 036 * can share the same coercion + error-formatting code without duplicating the lookup logic: 037 * 038 * <ol> 039 * <li>{@link #findValueExpression(List)} — given an annotation list, return the first {@code @Value} 040 * expression (or {@code null} if no {@code @Value} is present). 041 * <li>{@link #checkInjectConflict(List, String)} — throw {@link BeanCreationException} if both 042 * {@code @Value} and {@code @Inject}/{@code @Autowired} are present on the same site. 043 * <li>{@link #resolve(String, Class, String)} — pass the expression through {@code VarResolver}, 044 * coerce to the target type via {@link Settings#toType(String, Class)}, and wrap failures in 045 * {@link BeanCreationException}. 046 * </ol> 047 * 048 * <p> 049 * The resolver intentionally uses {@link VarResolver#DEFAULT}, which already includes 050 * {@code PropertyVar} (the {@code $P{key[,default]}} variable that reads from {@link Settings}). 051 * After the property-variable unification, the {@code ${key}} shortcut routes through that same {@code PropertyVar}, 052 * so {@code @Value("${db.url}")} and {@code @Value("$P{db.url}")} are equivalent. 053 */ 054public final class ValueResolver { 055 056 private ValueResolver() {} 057 058 /** 059 * Per-expression {@link VarTemplate} cache shared by all {@code @Value} injection sites. 060 * The cache key is the {@code @Value} expression text — 061 * which is bounded by the number of distinct expressions in the application, not by user 062 * input — so the unbounded map cannot grow uncontrollably in practice. Compilation 063 * happens at most once per distinct expression; subsequent {@code @Value} resolutions 064 * read the cached {@link VarTemplate} and skip the tokenizer + var-registry-lookup 065 * pipeline entirely. 066 */ 067 private static final ConcurrentMap<String,VarTemplate> TEMPLATE_CACHE = new ConcurrentHashMap<>(); 068 069 /** 070 * Returns the compiled {@link VarTemplate} for the given expression, computing and caching 071 * on first use. Intended for use by reflection-layer injection sites 072 * ({@link FieldInfo#inject}, {@link ParameterInfo#resolveValue}) that want to amortize 073 * tokenization across repeated bean constructions. 074 * 075 * @param expression The {@code @Value} expression text. Must not be {@code null}. 076 * @return The cached compiled template (uses {@link VarResolver#DEFAULT}). 077 */ 078 public static VarTemplate getCompiledTemplate(String expression) { 079 return TEMPLATE_CACHE.computeIfAbsent(expression, VarResolver.DEFAULT::compile); 080 } 081 082 /** 083 * Clears the internal {@link VarTemplate} cache. Primarily intended for tests that need to 084 * exercise the compile-time path repeatedly with different {@link VarResolver}s. Production 085 * code should not need to call this. 086 */ 087 public static void clearTemplateCache() { 088 TEMPLATE_CACHE.clear(); 089 } 090 091 /** 092 * Returns the {@code value()} expression carried by the first {@code @Value} annotation in the list, 093 * or {@code null} if no {@code @Value} (Juneau or Spring) is present. 094 * 095 * @param annotations The annotation list (from {@link FieldInfo#getAnnotations()}, 096 * {@link ParameterInfo#getAnnotations()}, etc.). May be {@code null} or empty, in which case {@code null} is returned. 097 * @return The expression, or {@code null} if not a {@code @Value} site. 098 */ 099 public static String findValueExpression(List<? extends AnnotationInfo<?>> annotations) { 100 if (isEmpty(annotations)) 101 return null; 102 return annotations.stream() 103 .map(JsrSupport::valueExpression) 104 .filter(Objects::nonNull) 105 .findFirst() 106 .orElse(null); 107 } 108 109 /** 110 * Returns {@code true} if any annotation in the list is a {@code @Value} marker. 111 * 112 * @param annotations The annotation list. May be {@code null} or empty, in which case {@code false} is returned. 113 * @return {@code true} if at least one annotation matches {@link JsrSupport#isValueAnnotation(AnnotationInfo)}. 114 */ 115 public static boolean hasValueAnnotation(List<? extends AnnotationInfo<?>> annotations) { 116 if (isEmpty(annotations)) 117 return false; 118 return annotations.stream().anyMatch(JsrSupport::isValueAnnotation); 119 } 120 121 /** 122 * Throws {@link BeanCreationException} if an injection site carries both {@code @Value} and an 123 * {@code @Inject}/{@code @Autowired} marker. 124 * 125 * <p> 126 * {@code @Value} resolves strings/primitives; {@code @Inject} resolves beans. Combining them on 127 * the same site is ambiguous — fail fast with a clear message. 128 * 129 * @param annotations The annotation list. May be {@code null} or empty, in which case this method returns without action. 130 * @param siteDescription A human-readable description of the site (used in the error message). 131 */ 132 public static void checkInjectConflict(List<? extends AnnotationInfo<?>> annotations, String siteDescription) { 133 if (isEmpty(annotations)) 134 return; 135 var hasValue = annotations.stream().anyMatch(JsrSupport::isValueAnnotation); 136 if (! hasValue) 137 return; 138 var hasInject = annotations.stream().anyMatch(JsrSupport::isInjectAnnotation); 139 if (hasInject) 140 throw new BeanCreationException("@Value and @Inject are mutually exclusive on " + siteDescription); 141 } 142 143 /** 144 * Resolves a {@code @Value} expression against the default {@link VarResolver} and coerces the 145 * result to {@code targetType} via {@link Settings#toType(String, Class)}. 146 * 147 * <p> 148 * Lookup semantics: 149 * <ul> 150 * <li>If the resolved value is {@code null} and {@code targetType} is a primitive, throws 151 * {@link BeanCreationException} (primitives cannot be {@code null}). 152 * <li>If the resolved value is {@code null} and {@code targetType} is a reference type, returns 153 * {@code null}. 154 * <li>If coercion via {@link Settings#toType(String, Class)} fails, the underlying exception is 155 * wrapped in {@link BeanCreationException} with a message that includes the expression, the 156 * resolved string, and the target type. 157 * </ul> 158 * 159 * @param expression The {@code @Value} expression (typically {@code "${key}"} or {@code "${key:default}"}). May be {@code null}, in which case {@code null} is returned for reference types (and a {@link BeanCreationException} is thrown for primitive target types). 160 * @param targetType The target Java type. May be a primitive. Must not be {@code null}. 161 * @param siteDescription A human-readable description of the site (used in error messages). 162 * @return The coerced value. 163 */ 164 public static Object resolve(String expression, Class<?> targetType, String siteDescription) { 165 return resolve(expression, targetType, siteDescription, null); 166 } 167 168 /** 169 * Same as {@link #resolve(String, Class, String)} but consults caller-scoped 170 * {@link PropertySource} beans resolved from the supplied {@link BeanStore} between 171 * the {@link Settings} local/global override stores and the {@link Settings} sources chain. 172 * 173 * <p> 174 * The {@code beanStore} parameter is preserved by-reference; if its 175 * {@code PropertySource}-typed contents change between calls, the next resolution picks up 176 * the current state. Passing {@code null} (or a {@code BeanStore} with no 177 * {@code PropertySource}-typed beans) is byte-for-byte equivalent to the zero-argument 178 * overload — no extra allocation and no behavior change. 179 * 180 * @param expression The {@code @Value} expression. May be {@code null}, in which case {@code null} is returned for reference types (and a {@link BeanCreationException} is thrown for primitive target types). 181 * @param targetType The target Java type. May be a primitive. Must not be {@code null}. 182 * @param siteDescription A human-readable description of the site (used in error messages). 183 * @param beanStore Caller-scoped {@link BeanStore} consulted for {@link PropertySource} beans. 184 * May be {@code null}. 185 * @return The coerced value. 186 */ 187 public static Object resolve(String expression, Class<?> targetType, String siteDescription, BeanStore beanStore) { 188 if (expression == null) 189 return resolveCoerce(null, targetType, expression, siteDescription); 190 var template = getCompiledTemplate(expression); 191 var session = VarResolver.DEFAULT.createSession(); 192 var sources = scopedSources(beanStore); 193 if (sources != null) 194 session.bean(PropertySource[].class, sources); 195 var resolved = template.resolve(session); 196 return resolveCoerce(resolved, targetType, expression, siteDescription); 197 } 198 199 /** 200 * Generic-aware {@code @Value} resolution that honors {@link Supplier}{@code <T>} 201 * field types by returning a re-evaluating, threadsafe Supplier instead of a one-shot 202 * resolved value (field-type autodetect — bare {@code String} resolves once, {@code Supplier<T>} 203 * re-evaluates per {@code .get()}). 204 * 205 * <p> 206 * Behavior by {@code genericTargetType}: 207 * <ul> 208 * <li>{@code Supplier<String>} → returns a {@link Supplier} that re-evaluates the 209 * compiled template on every {@link Supplier#get()} call, each time against a 210 * fresh {@link VarResolverSession}. Safe to share across threads. 211 * <li>{@code Supplier<T>} for {@code T != String} → returns a {@link Supplier} that, on every 212 * {@link Supplier#get()} call, re-evaluates the template <i>and</i> coerces the resolved 213 * string into {@code T} via the same {@code String -> T} converter machinery that plain 214 * {@code @Value T} uses ({@link Settings#toType(String, Class)}). The coerced value is never 215 * memoized (late-binding contract); coercion failures surface as a {@link RuntimeException} 216 * from {@code .get()} naming the {@code @Value} key and target type. {@code T} is taken from 217 * any type the converter registry can build from a {@code String} (e.g. {@code Integer}, 218 * {@code Long}, {@code Boolean}, {@code Duration}, {@code URI}, {@code Path}, enums). 219 * <li>Anything else (including raw {@code Supplier} and {@code Optional<Supplier<T>>}) → delegates 220 * to {@link #resolve(String, Class, String)} with the raw target class (preserves existing 221 * behavior; the {@code Optional<Supplier<T>>} nesting is intentionally not specially handled). 222 * </ul> 223 * 224 * @param expression The {@code @Value} expression. May be {@code null}. 225 * @param targetType The erased target class (e.g. {@code String.class} or {@code Supplier.class}). Must not be {@code null}. 226 * @param genericTargetType The declared generic type (e.g. {@code Supplier<Integer>}). May be 227 * {@code null}, in which case Supplier-detection is skipped. 228 * @param siteDescription Human-readable site description for error messages. 229 * @return Either a one-shot coerced value, or a {@code Supplier<T>} factory. 230 */ 231 public static Object resolve(String expression, Class<?> targetType, Type genericTargetType, String siteDescription) { 232 return resolve(expression, targetType, genericTargetType, siteDescription, null); 233 } 234 235 /** 236 * Same as {@link #resolve(String, Class, Type, String)} but consults caller-scoped 237 * {@link PropertySource} beans resolved from the supplied {@link BeanStore}. 238 * 239 * <p> 240 * The {@code beanStore} reference is captured (not snapshotted) by the returned 241 * {@code Supplier<T>} when the declared field/parameter type is {@code Supplier<...>}, 242 * so re-evaluating reads always see the current state of {@code beanStore.getBeansOfType(PropertySource.class)}. 243 * 244 * @param expression The {@code @Value} expression. May be {@code null}. 245 * @param targetType The erased target class. Must not be {@code null}. 246 * @param genericTargetType The declared generic type. May be {@code null}. 247 * @param siteDescription Human-readable site description for error messages. 248 * @param beanStore Caller-scoped {@link BeanStore} consulted for {@link PropertySource} beans. 249 * May be {@code null}. 250 * @return Either a one-shot coerced value, or a {@code Supplier<T>} factory. 251 */ 252 public static Object resolve(String expression, Class<?> targetType, Type genericTargetType, String siteDescription, 253 BeanStore beanStore) { 254 var elementType = supplierElementType(targetType, genericTargetType); 255 if (elementType != null) { 256 // Build the re-evaluating string Supplier once. This is the same factory the bare 257 // Supplier<String> path has always used. 258 var stringSupplier = buildStringSupplier(expression, beanStore); 259 if (elementType == String.class) 260 return stringSupplier; 261 // Supplier<T> for T != String: coerce per .get() using the same String -> T converter 262 // machinery that plain @Value T uses (resolveCoerce -> Settings.toType). Per the 263 // late-binding contract, the string is re-resolved AND re-coerced on every .get() — the 264 // coerced value is never memoized. Only the converter lookup is cached (Settings.toType 265 // memoizes the discovered String -> T function per type in its toTypeFunctions registry, 266 // so each .get() pays the coercion but not the reflective converter discovery). A coercion 267 // failure surfaces as a BeanCreationException (a RuntimeException) from .get(), naming the 268 // @Value expression and the target type. 269 final Class<?> et = elementType; 270 return (Supplier<Object>) () -> resolveCoerce(stringSupplier.get(), et, expression, siteDescription); 271 } 272 return resolve(expression, targetType, siteDescription, beanStore); 273 } 274 275 /** 276 * Builds the re-evaluating {@code Supplier<String>} used by the {@code @Value Supplier<...>} 277 * autodetect path. Each {@link Supplier#get()} re-resolves the compiled template against a fresh 278 * {@link VarResolverSession}, so live config changes are reflected (the late-binding contract). 279 * 280 * @param expression The {@code @Value} expression. May be {@code null}. 281 * @param beanStore Caller-scoped {@link BeanStore} consulted for {@link PropertySource} beans. May be {@code null}. 282 * @return A threadsafe, re-evaluating {@code Supplier<String>}. 283 */ 284 private static Supplier<String> buildStringSupplier(String expression, BeanStore beanStore) { 285 if (expression == null) 286 return () -> null; 287 var template = getCompiledTemplate(expression); 288 // Literal templates fast-path: capture the resolved string once and hand back a 289 // constant Supplier. Saves a per-.get() session allocation when the expression 290 // has no variables (e.g. @Value("literal") Supplier<String>). 291 if (template.isLiteral()) { 292 var literal = template.resolve(VarResolver.DEFAULT.createSession()); 293 return () -> literal; 294 } 295 if (beanStore == null) 296 return template.asSupplierWithFreshSessions(VarResolver.DEFAULT); 297 // Capture the BeanStore by-reference so re-evaluating reads see the current state of 298 // its PropertySource-typed beans (matches the "by-reference, not snapshot" contract). 299 final BeanStore scope = beanStore; 300 return () -> { 301 var s = VarResolver.DEFAULT.createSession(); 302 var sources = scopedSources(scope); 303 if (sources != null) 304 s.bean(PropertySource[].class, sources); 305 return template.resolve(s); 306 }; 307 } 308 309 /** 310 * Returns the caller-scoped {@link PropertySource} array for the supplied {@link BeanStore}, 311 * or {@code null} if the store has no {@code PropertySource}-typed beans (matching the 312 * "no behavior change when no scoped sources are present" contract). 313 * 314 * <p> 315 * Walks {@link BeanStore#getBeansOfType(Class) beanStore.getBeansOfType(PropertySource.class)}. 316 * The returned array preserves the iteration order of {@code getBeansOfType} (parent-chain 317 * beans before local beans before overriding-parent beans, then sorted by 318 * {@code @Order/@Primary/@Bean.priority()}). 319 */ 320 @SuppressWarnings({ 321 "java:S1168" // Null is a meaningful "no scoped sources" signal that callers branch on to preserve the no-allocation / no-behavior-change contract; an empty array would alter behavior. 322 }) 323 private static PropertySource[] scopedSources(BeanStore beanStore) { 324 if (beanStore == null) 325 return null; 326 var map = beanStore.getBeansOfType(PropertySource.class); 327 if (map.isEmpty()) 328 return null; 329 return map.values().toArray(new PropertySource[0]); 330 } 331 332 /** 333 * Returns the {@code T} element class of a declared {@code Supplier<T>} injection site, or 334 * {@code null} if the declared type is not a coercible {@code Supplier<T>}. 335 * 336 * <p> 337 * Returns {@code null} (i.e. "not a typed Supplier site, fall through to one-shot resolution") 338 * for raw {@code Supplier}, non-{@code Supplier} parameterized types such as 339 * {@code Optional<Supplier<T>>} (whose raw type is {@code Optional}, not {@code Supplier} — the 340 * nesting is intentionally not specially handled), and type arguments that are not a concrete 341 * {@link Class} (wildcards / type variables). {@code Supplier<String>} returns {@code String.class}, 342 * which the caller treats as the pre-existing pass-through behavior. 343 */ 344 private static Class<?> supplierElementType(Class<?> targetType, Type genericTargetType) { 345 if (targetType != Supplier.class) 346 return null; 347 if (!(genericTargetType instanceof ParameterizedType genericTargetType2)) 348 return null; 349 if (genericTargetType2.getRawType() != Supplier.class) 350 return null; 351 var args = genericTargetType2.getActualTypeArguments(); 352 if (args.length != 1) 353 return null; 354 return args[0] instanceof Class<?> c ? c : null; 355 } 356 357 private static Object resolveCoerce(String resolved, Class<?> targetType, String expression, String siteDescription) { 358 var effectiveType = box(targetType); 359 360 if (ie(resolved)) { 361 if (targetType.isPrimitive()) 362 throw new BeanCreationException("Could not resolve required @Value('" + expression + "') for primitive site " + siteDescription); 363 if (resolved == null) 364 return null; 365 if (effectiveType == String.class) 366 return resolved; 367 } 368 369 // Character has no usable valueOf(String) — Settings.toType can't coerce; handle manually. 370 if (effectiveType == Character.class) { 371 if (resolved.length() == 1) 372 return resolved.charAt(0); 373 throw new BeanCreationException( 374 "Could not coerce @Value('" + expression + "') resolving to '" + resolved + "' to char for " + siteDescription); 375 } 376 377 try { 378 return Settings.get().toType(resolved, effectiveType); 379 } catch (RuntimeException e) { 380 throw new BeanCreationException( 381 "Could not coerce @Value('" + expression + "') resolving to '" + resolved + "' to " 382 + targetType.getName() + " for " + siteDescription, 383 e); 384 } 385 } 386 387 /** 388 * Returns the boxed counterpart of a primitive type, or {@code type} itself if not primitive. 389 * 390 * <p> 391 * {@link Settings#toType(String, Class)} uses reflection for {@code valueOf(String)} / {@code (String)} 392 * constructor lookup, which doesn't work for raw primitives ({@code int.class} etc.). Box them first. 393 * 394 * @param type The (possibly primitive) target type. 395 * @return The boxed equivalent, or {@code type} unchanged. 396 */ 397 private static Class<?> box(Class<?> type) { 398 if (! type.isPrimitive()) 399 return type; 400 if (type == int.class) return Integer.class; 401 if (type == long.class) return Long.class; 402 if (type == boolean.class) return Boolean.class; 403 if (type == double.class) return Double.class; 404 if (type == float.class) return Float.class; 405 if (type == short.class) return Short.class; 406 if (type == byte.class) return Byte.class; 407 if (type == char.class) return Character.class; 408 return type; // void or unreachable 409 } 410}