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.cp; 018 019import static java.util.stream.Collectors.*; 020import static java.util.stream.Collectors.toList; 021import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 022import static org.apache.juneau.commons.utils.CollectionUtils.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024import static org.apache.juneau.commons.utils.Utils.*; 025 026import java.util.*; 027import java.util.concurrent.*; 028 029import java.util.function.*; 030import java.util.stream.*; 031 032import org.apache.juneau.commons.collections.*; 033import org.apache.juneau.commons.concurrent.*; 034import org.apache.juneau.commons.reflect.*; 035 036/** 037 * Java bean store. 038 * 039 * <p> 040 * A simple storage database for beans keyed by type and name. 041 * Used to retrieve and instantiate beans using an injection-like API. 042 * It's similar in concept to the injection framework of Spring but greatly simplified in function and not intended to implement a full-fledged injection framework. 043 * 044 * <p> 045 * Beans can be stored with or without names. Named beans are typically resolved using 046 * the <ja>@Name</ja> or <ja>@Qualified</ja> annotations on constructor or method parameters. 047 * 048 * <p> 049 * Beans are added through the following methods: 050 * <ul class='javatreec'> 051 * <li class='jm'>{@link #add(Class,Object) add(Class,Object)} 052 * <li class='jm'>{@link #add(Class,Object,String) add(Class,Object,String)} 053 * <li class='jm'>{@link #addBean(Class,Object) addBean(Class,Object)} 054 * <li class='jm'>{@link #addBean(Class,Object,String) addBean(Class,Object,String)} 055 * <li class='jm'>{@link #addSupplier(Class,Supplier) addSupplier(Class,Supplier)} 056 * <li class='jm'>{@link #addSupplier(Class,Supplier,String) addSupplier(Class,Supplier,String)} 057 * </ul> 058 * 059 * <p> 060 * Beans are retrieved through the following methods: 061 * <ul class='javatreec'> 062 * <li class='jm'>{@link #getBean(Class) getBean(Class)} 063 * <li class='jm'>{@link #getBean(Class,String) getBean(Class,String)} 064 * <li class='jm'>{@link #stream(Class) stream(Class)} 065 * </ul> 066 * 067 * <p> 068 * Beans are created through the following methods: 069 * <ul class='javatreec'> 070 * <li class='jm'>{@link #createBean(Class) createBean(Class)} 071 * <li class='jm'>{@link #createMethodFinder(Class) createMethodFinder(Class)} 072 * <li class='jm'>{@link #createMethodFinder(Class,Class) createMethodFinder(Class,Class)} 073 * <li class='jm'>{@link #createMethodFinder(Class,Object) createMethodFinder(Class,Object)} 074 * </ul> 075 * 076 * <h5 class='section'>Notes:</h5><ul> 077 * <li class='note'>Bean stores can be nested using {@link Builder#parent(BeanStore)}. 078 * <li class='note'>Bean stores can be made read-only using {@link Builder#readOnly()}. 079 * <li class='note'>Bean stores can be made thread-safe using {@link Builder#threadSafe()}. 080 * </ul> 081 * 082 */ 083public class BeanStore { 084 /** 085 * Builder class. 086 */ 087 public static class Builder { 088 089 BeanStore parent; 090 boolean readOnly, threadSafe; 091 Object outer; 092 Class<? extends BeanStore> type; 093 BeanStore impl; 094 095 /** 096 * Constructor. 097 */ 098 protected Builder() {} 099 100 /** 101 * Instantiates this bean store. 102 * 103 * @return A new bean store. 104 */ 105 public BeanStore build() { 106 if (nn(impl)) 107 return impl; 108 if (type == null || type == BeanStore.class) 109 return new BeanStore(this); 110 111 var c = info(type); 112 113 // @formatter:off 114 var result = c.getDeclaredMethod( 115 x -> x.isPublic() 116 && x.getParameterCount() == 0 117 && x.isStatic() 118 && x.hasName("getInstance") 119 ).map(m -> m.<BeanStore>invoke(null)); 120 // @formatter:on 121 if (result.isPresent()) 122 return result.get(); 123 124 result = c.getPublicConstructor(x -> x.canAccept(this)).map(ci -> ci.<BeanStore>newInstance(this)); 125 if (result.isPresent()) 126 return result.get(); 127 128 result = c.getDeclaredConstructor(x -> x.isProtected() && x.canAccept(this)).map(ci -> ci.accessible().<BeanStore>newInstance(this)); 129 if (result.isPresent()) 130 return result.get(); 131 132 throw rex("Could not find a way to instantiate class {0}", cn(type)); 133 } 134 135 /** 136 * Overrides the bean to return from the {@link #build()} method. 137 * 138 * @param value The bean to return from the {@link #build()} method. 139 * @return This object. 140 */ 141 public Builder impl(BeanStore value) { 142 impl = value; 143 return this; 144 } 145 146 /** 147 * Specifies the outer bean context. 148 * 149 * <p> 150 * The outer context bean to use when calling constructors on inner classes. 151 * 152 * @param value The outer bean context. Can be <jk>null</jk>. 153 * @return This object. 154 */ 155 public Builder outer(Object value) { 156 outer = value; 157 return this; 158 } 159 160 /** 161 * Specifies the parent bean store. 162 * 163 * <p> 164 * Bean searches are performed recursively up this parent chain. 165 * 166 * @param value The setting value. 167 * @return This object. 168 */ 169 public Builder parent(BeanStore value) { 170 parent = value; 171 return this; 172 } 173 174 /** 175 * Specifies that the bean store is read-only. 176 * 177 * <p> 178 * This means methods such as {@link BeanStore#addBean(Class, Object)} cannot be used. 179 * 180 * @return This object. 181 */ 182 public Builder readOnly() { 183 readOnly = true; 184 return this; 185 } 186 187 /** 188 * Specifies that the bean store being created should be thread-safe. 189 * 190 * @return This object. 191 */ 192 public Builder threadSafe() { 193 threadSafe = true; 194 return this; 195 } 196 197 /** 198 * Overrides the bean store type. 199 * 200 * <p> 201 * The specified type must have one of the following: 202 * <ul> 203 * <li>A static <c>getInstance()</c> method. 204 * <li>A public constructor that takes in this builder. 205 * <li>A protected constructor that takes in this builder. 206 * </ul> 207 * 208 * @param value The bean store type. 209 * @return This object. 210 */ 211 public Builder type(Class<? extends BeanStore> value) { 212 type = value; 213 return this; 214 } 215 } 216 217 /** 218 * Non-existent bean store. 219 */ 220 public static final class Void extends BeanStore {} 221 222 /** 223 * Static read-only reusable instance. 224 */ 225 public static final BeanStore INSTANCE = create().readOnly().build(); 226 227 /** 228 * Static creator. 229 * 230 * @return A new {@link Builder} object. 231 */ 232 public static Builder create() { 233 return new Builder(); 234 } 235 236 /** 237 * Static creator. 238 * 239 * @param parent Parent bean store. Can be <jk>null</jk> if this is the root resource. 240 * @return A new {@link BeanStore} object. 241 */ 242 public static BeanStore of(BeanStore parent) { 243 return create().parent(parent).build(); 244 } 245 246 /** 247 * Static creator. 248 * 249 * @param parent Parent bean store. Can be <jk>null</jk> if this is the root resource. 250 * @param outer The outer bean used when instantiating inner classes. Can be <jk>null</jk>. 251 * @return A new {@link BeanStore} object. 252 */ 253 public static BeanStore of(BeanStore parent, Object outer) { 254 return create().parent(parent).outer(outer).build(); 255 } 256 257 private final Deque<BeanStoreEntry<?>> entries; 258 private final Map<Class<?>,BeanStoreEntry<?>> unnamedEntries; 259 260 final Optional<BeanStore> parent; 261 final Optional<Object> outer; 262 final boolean readOnly, threadSafe; 263 final SimpleReadWriteLock lock; 264 265 /** 266 * Constructor. 267 * 268 * @param builder The builder containing the settings for this bean. 269 */ 270 protected BeanStore(Builder builder) { 271 parent = opt(builder.parent); 272 outer = opt(builder.outer); 273 readOnly = builder.readOnly; 274 threadSafe = builder.threadSafe; 275 lock = threadSafe ? new SimpleReadWriteLock() : SimpleReadWriteLock.NO_OP; 276 entries = threadSafe ? new ConcurrentLinkedDeque<>() : new LinkedList<>(); 277 unnamedEntries = threadSafe ? new ConcurrentHashMap<>() : map(); 278 } 279 280 BeanStore() { 281 this(create()); 282 } 283 284 /** 285 * Same as {@link #addBean(Class,Object)} but returns the bean instead of this object for fluent calls. 286 * 287 * @param <T> The class to associate this bean with. 288 * @param beanType The class to associate this bean with. 289 * @param bean The bean. Can be <jk>null</jk>. 290 * @return The bean. 291 */ 292 public <T> T add(Class<T> beanType, T bean) { 293 add(beanType, bean, null); 294 return bean; 295 } 296 297 /** 298 * Same as {@link #addBean(Class,Object,String)} but returns the bean instead of this object for fluent calls. 299 * 300 * @param <T> The class to associate this bean with. 301 * @param beanType The class to associate this bean with. 302 * @param bean The bean. Can be <jk>null</jk>. 303 * @param name The bean name if this is a named bean. Can be <jk>null</jk>. 304 * @return The bean. 305 */ 306 public <T> T add(Class<T> beanType, T bean, String name) { 307 addBean(beanType, bean, name); 308 return bean; 309 } 310 311 /** 312 * Adds an unnamed bean of the specified type to this factory. 313 * 314 * @param <T> The class to associate this bean with. 315 * @param beanType The class to associate this bean with. 316 * @param bean The bean. Can be <jk>null</jk>. 317 * @return This object. 318 */ 319 public <T> BeanStore addBean(Class<T> beanType, T bean) { 320 return addBean(beanType, bean, null); 321 } 322 323 /** 324 * Adds a named bean of the specified type to this factory. 325 * 326 * @param <T> The class to associate this bean with. 327 * @param beanType The class to associate this bean with. 328 * @param bean The bean. Can be <jk>null</jk>. 329 * @param name The bean name if this is a named bean. Can be <jk>null</jk>. 330 * @return This object. 331 */ 332 public <T> BeanStore addBean(Class<T> beanType, T bean, String name) { 333 return addSupplier(beanType, () -> bean, name); 334 } 335 336 /** 337 * Adds a supplier for an unnamed bean of the specified type to this factory. 338 * 339 * @param <T> The class to associate this bean with. 340 * @param beanType The class to associate this bean with. 341 * @param bean The bean supplier. 342 * @return This object. 343 */ 344 public <T> BeanStore addSupplier(Class<T> beanType, Supplier<T> bean) { 345 return addSupplier(beanType, bean, null); 346 } 347 348 /** 349 * Adds a supplier for a named bean of the specified type to this factory. 350 * 351 * @param <T> The class to associate this bean with. 352 * @param beanType The class to associate this bean with. 353 * @param bean The bean supplier. 354 * @param name The bean name if this is a named bean. Can be <jk>null</jk>. 355 * @return This object. 356 */ 357 public <T> BeanStore addSupplier(Class<T> beanType, Supplier<T> bean, String name) { 358 assertCanWrite(); 359 var e = createEntry(beanType, bean, name); 360 try (var x = lock.write()) { 361 entries.addFirst(e); 362 if (e(name)) 363 unnamedEntries.put(beanType, e); 364 } 365 return this; 366 } 367 368 /** 369 * Clears out all bean in this bean store. 370 * 371 * <p> 372 * Does not affect the parent bean store. 373 * 374 * @return This object. 375 */ 376 public BeanStore clear() { 377 assertCanWrite(); 378 try (var x = lock.write()) { 379 unnamedEntries.clear(); 380 entries.clear(); 381 } 382 return this; 383 } 384 385 /** 386 * Instantiates a bean creator. 387 * 388 * <h5 class='section'>See Also:</h5><ul> 389 * <li class='jc'>{@link BeanCreator} for usage. 390 * </ul> 391 * 392 * @param <T> The bean type to create. 393 * @param beanType The bean type to create. 394 * @return A new bean creator. 395 */ 396 public <T> BeanCreator<T> createBean(Class<T> beanType) { 397 return new BeanCreator<>(beanType, this); 398 } 399 400 /** 401 * Create a method finder for finding bean creation methods. 402 * 403 * <p> 404 * Same as {@link #createMethodFinder(Class,Object)} but uses {@link Builder#outer(Object)} as the resource bean. 405 * 406 * <h5 class='section'>See Also:</h5><ul> 407 * <li class='jc'>{@link BeanCreateMethodFinder} for usage. 408 * </ul> 409 * 410 * @param <T> The bean type to create. 411 * @param beanType The bean type to create. 412 * @return The method finder. Never <jk>null</jk>. 413 */ 414 public <T> BeanCreateMethodFinder<T> createMethodFinder(Class<T> beanType) { 415 return new BeanCreateMethodFinder<>(beanType, outer.orElseThrow(() -> new IllegalArgumentException("Method cannot be used without outer bean definition.")), this); 416 } 417 418 /** 419 * Create a method finder for finding bean creation methods. 420 * 421 * <p> 422 * Same as {@link #createMethodFinder(Class,Class)} but looks for only static methods on the specified resource class 423 * and not also instance methods within the context of a bean. 424 * 425 * <h5 class='section'>See Also:</h5><ul> 426 * <li class='jc'>{@link BeanCreateMethodFinder} for usage. 427 * </ul> 428 * 429 * @param <T> The bean type to create. 430 * @param beanType The bean type to create. 431 * @param resourceClass The class containing the bean creator method. 432 * @return The method finder. Never <jk>null</jk>. 433 */ 434 public <T> BeanCreateMethodFinder<T> createMethodFinder(Class<T> beanType, Class<?> resourceClass) { 435 return new BeanCreateMethodFinder<>(beanType, resourceClass, this); 436 } 437 438 /** 439 * Create a method finder for finding bean creation methods. 440 * 441 * <h5 class='section'>See Also:</h5><ul> 442 * <li class='jc'>{@link BeanCreateMethodFinder} for usage. 443 * </ul> 444 * 445 * @param <T> The bean type to create. 446 * @param beanType The bean type to create. 447 * @param resource The class containing the bean creator method. 448 * @return The method finder. Never <jk>null</jk>. 449 */ 450 public <T> BeanCreateMethodFinder<T> createMethodFinder(Class<T> beanType, Object resource) { 451 return new BeanCreateMethodFinder<>(beanType, resource, this); 452 } 453 454 /** 455 * Returns the unnamed bean of the specified type. 456 * 457 * @param <T> The type of bean to return. 458 * @param beanType The type of bean to return. 459 * @return The bean. 460 */ 461 @SuppressWarnings("unchecked") 462 public <T> Optional<T> getBean(Class<T> beanType) { 463 try (var x = lock.read()) { 464 var e = (BeanStoreEntry<T>)unnamedEntries.get(beanType); 465 if (nn(e)) 466 return opt(e.get()); 467 if (parent.isPresent()) 468 return parent.get().getBean(beanType); 469 return opte(); 470 } 471 } 472 473 /** 474 * Returns the named bean of the specified type. 475 * 476 * @param <T> The type of bean to return. 477 * @param beanType The type of bean to return. 478 * @param name The bean name. Can be <jk>null</jk>. 479 * @return The bean. 480 */ 481 @SuppressWarnings("unchecked") 482 public <T> Optional<T> getBean(Class<T> beanType, String name) { 483 try (var x = lock.read()) { 484 var e = (BeanStoreEntry<T>)entries.stream().filter(x2 -> x2.matches(beanType, name)).findFirst().orElse(null); 485 if (nn(e)) 486 return opt(e.get()); 487 if (parent.isPresent()) 488 return parent.get().getBean(beanType, name); 489 return opte(); 490 } 491 } 492 493 /** 494 * Given an executable, returns a list of types that are missing from this factory. 495 * 496 * @param executable The constructor or method to get the params for. 497 * @return A comma-delimited list of types that are missing from this factory, or <jk>null</jk> if none are missing. 498 */ 499 public String getMissingParams(ExecutableInfo executable) { 500 var params = executable.getParameters(); 501 List<String> l = list(); 502 loop: for (int i = 0; i < params.size(); i++) { 503 var pi = params.get(i); 504 var pt = pi.getParameterType(); 505 if (i == 0 && outer.isPresent() && pt.isInstance(outer.get())) 506 continue loop; 507 if (pt.is(Optional.class) || pt.is(BeanStore.class)) 508 continue loop; 509 var beanName = pi.getResolvedQualifier(); // Use @Named for bean injection 510 var ptc = pt.inner(); 511 if (beanName == null && ! hasBean(ptc)) 512 l.add(pt.getNameSimple()); 513 if (nn(beanName) && ! hasBean(ptc, beanName)) 514 l.add(pt.getNameSimple() + '@' + beanName); 515 } 516 return l.isEmpty() ? null : l.stream().sorted().collect(joining(",")); 517 } 518 519 /** 520 * Returns the corresponding beans in this factory for the specified param types. 521 * 522 * @param executable The constructor or method to get the params for. 523 * @return The corresponding beans in this factory for the specified param types. 524 */ 525 public Object[] getParams(ExecutableInfo executable) { 526 var o = new Object[executable.getParameterCount()]; 527 for (var i = 0; i < executable.getParameterCount(); i++) { 528 var pi = executable.getParameter(i); 529 var pt = pi.getParameterType(); 530 if (i == 0 && outer.isPresent() && pt.isInstance(outer.get())) { 531 o[i] = outer.get(); 532 } else if (pt.is(BeanStore.class)) { 533 o[i] = this; 534 } else { 535 var beanQualifier = pi.getResolvedQualifier(); 536 var ptc = pt.unwrap(Optional.class).inner(); 537 var o2 = beanQualifier == null ? getBean(ptc) : getBean(ptc, beanQualifier); 538 o[i] = pt.is(Optional.class) ? o2 : o2.orElse(null); 539 } 540 } 541 return o; 542 } 543 544 /** 545 * Given the list of param types, returns <jk>true</jk> if this factory has all the parameters for the specified executable. 546 * 547 * @param executable The constructor or method to get the params for. 548 * @return A comma-delimited list of types that are missing from this factory. 549 */ 550 public boolean hasAllParams(ExecutableInfo executable) { 551 loop: for (int i = 0; i < executable.getParameterCount(); i++) { 552 var pi = executable.getParameter(i); 553 var pt = pi.getParameterType(); 554 if (i == 0 && outer.isPresent() && pt.isInstance(outer.get())) 555 continue loop; 556 if (pt.is(Optional.class) || pt.is(BeanStore.class)) 557 continue loop; 558 var beanQualifier = pi.getResolvedQualifier(); 559 var ptc = pt.inner(); 560 if ((beanQualifier == null && ! hasBean(ptc)) || (nn(beanQualifier) && ! hasBean(ptc, beanQualifier))) 561 return false; 562 } 563 return true; 564 } 565 566 /** 567 * Returns <jk>true</jk> if this store contains the specified unnamed bean type. 568 * 569 * @param beanType The bean type to check. 570 * @return <jk>true</jk> if this store contains the specified unnamed bean type. 571 */ 572 public boolean hasBean(Class<?> beanType) { 573 return unnamedEntries.containsKey(beanType) || parent.map(x -> x.hasBean(beanType)).orElse(false); 574 } 575 576 /** 577 * Returns <jk>true</jk> if this store contains the specified named bean type. 578 * 579 * @param beanType The bean type to check. 580 * @param name The bean name. 581 * @return <jk>true</jk> if this store contains the specified named bean type. 582 */ 583 public boolean hasBean(Class<?> beanType, String name) { 584 return entries.stream().anyMatch(x -> x.matches(beanType, name)) || parent.map(x -> x.hasBean(beanType, name)).orElse(false); 585 } 586 587 /** 588 * Removes an unnamed bean from this store. 589 * 590 * @param beanType The bean type being removed. 591 * @return This object. 592 */ 593 public BeanStore removeBean(Class<?> beanType) { 594 return removeBean(beanType, null); 595 } 596 597 /** 598 * Removes a named bean from this store. 599 * 600 * @param beanType The bean type being removed. 601 * @param name The bean name to remove. 602 * @return This object. 603 */ 604 public BeanStore removeBean(Class<?> beanType, String name) { 605 assertCanWrite(); 606 try (var x = lock.write()) { 607 if (name == null) 608 unnamedEntries.remove(beanType); 609 entries.removeIf(y -> y.matches(beanType, name)); 610 } 611 return this; 612 } 613 614 /** 615 * Returns all the beans in this store of the specified type. 616 * 617 * <p> 618 * Returns both named and unnamed beans. 619 * 620 * <p> 621 * The results from the parent bean store are appended to the list of beans from this beans store. 622 * 623 * @param <T> The bean type to return. 624 * @param beanType The bean type to return. 625 * @return The bean entries. Never <jk>null</jk>. 626 */ 627 public <T> Stream<BeanStoreEntry<T>> stream(Class<T> beanType) { 628 @SuppressWarnings("unchecked") 629 var s = entries.stream().filter(x -> x.matches(beanType)).map(x -> (BeanStoreEntry<T>)x); 630 if (parent.isPresent()) 631 s = Stream.concat(s, parent.get().stream(beanType)); 632 return s; 633 } 634 635 protected FluentMap<String,Object> properties() { 636 // @formatter:off 637 return filteredBeanPropertyMap() 638 .a("entries", entries.stream().map(BeanStoreEntry::properties).collect(toList())) 639 .a("identity", id(this)) 640 .a("outer", id(outer.orElse(null))) 641 .a("parent", parent.map(BeanStore::properties).orElse(null)) 642 .ai(readOnly, "readOnly", readOnly) 643 .ai(threadSafe, "threadSafe", threadSafe); 644 // @formatter:on 645 } 646 647 @Override /* Overridden from Object */ 648 public String toString() { 649 return r(properties()); 650 } 651 652 private void assertCanWrite() { 653 if (readOnly) 654 throw new IllegalStateException("Method cannot be used because BeanStore is read-only."); 655 } 656 657 /** 658 * Creates an entry in this store for the specified bean. 659 * 660 * <p> 661 * Subclasses can override this method to create their own entry subtypes. 662 * 663 * @param <T> The class type to associate with the bean. 664 * @param type The class type to associate with the bean. 665 * @param bean The bean supplier. 666 * @param name Optional name to associate with the bean. Can be <jk>null</jk>. 667 * @return A new bean store entry. 668 */ 669 protected <T> BeanStoreEntry<T> createEntry(Class<T> type, Supplier<T> bean, String name) { 670 return BeanStoreEntry.create(type, bean, name); 671 } 672}