1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.cp;
18
19 import static java.util.stream.Collectors.*;
20 import static java.util.stream.Collectors.toList;
21 import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
22 import static org.apache.juneau.commons.utils.CollectionUtils.*;
23 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
24 import static org.apache.juneau.commons.utils.Utils.*;
25
26 import java.util.*;
27 import java.util.concurrent.*;
28
29 import java.util.function.*;
30 import java.util.stream.*;
31
32 import org.apache.juneau.commons.collections.*;
33 import org.apache.juneau.commons.concurrent.*;
34 import org.apache.juneau.commons.reflect.*;
35
36 /**
37 * Java bean store.
38 *
39 * <p>
40 * A simple storage database for beans keyed by type and name.
41 * Used to retrieve and instantiate beans using an injection-like API.
42 * 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.
43 *
44 * <p>
45 * Beans can be stored with or without names. Named beans are typically resolved using
46 * the <ja>@Name</ja> or <ja>@Qualified</ja> annotations on constructor or method parameters.
47 *
48 * <p>
49 * Beans are added through the following methods:
50 * <ul class='javatreec'>
51 * <li class='jm'>{@link #add(Class,Object) add(Class,Object)}
52 * <li class='jm'>{@link #add(Class,Object,String) add(Class,Object,String)}
53 * <li class='jm'>{@link #addBean(Class,Object) addBean(Class,Object)}
54 * <li class='jm'>{@link #addBean(Class,Object,String) addBean(Class,Object,String)}
55 * <li class='jm'>{@link #addSupplier(Class,Supplier) addSupplier(Class,Supplier)}
56 * <li class='jm'>{@link #addSupplier(Class,Supplier,String) addSupplier(Class,Supplier,String)}
57 * </ul>
58 *
59 * <p>
60 * Beans are retrieved through the following methods:
61 * <ul class='javatreec'>
62 * <li class='jm'>{@link #getBean(Class) getBean(Class)}
63 * <li class='jm'>{@link #getBean(Class,String) getBean(Class,String)}
64 * <li class='jm'>{@link #stream(Class) stream(Class)}
65 * </ul>
66 *
67 * <p>
68 * Beans are created through the following methods:
69 * <ul class='javatreec'>
70 * <li class='jm'>{@link #createBean(Class) createBean(Class)}
71 * <li class='jm'>{@link #createMethodFinder(Class) createMethodFinder(Class)}
72 * <li class='jm'>{@link #createMethodFinder(Class,Class) createMethodFinder(Class,Class)}
73 * <li class='jm'>{@link #createMethodFinder(Class,Object) createMethodFinder(Class,Object)}
74 * </ul>
75 *
76 * <h5 class='section'>Notes:</h5><ul>
77 * <li class='note'>Bean stores can be nested using {@link Builder#parent(BeanStore)}.
78 * <li class='note'>Bean stores can be made read-only using {@link Builder#readOnly()}.
79 * <li class='note'>Bean stores can be made thread-safe using {@link Builder#threadSafe()}.
80 * </ul>
81 *
82 */
83 public class BeanStore {
84 /**
85 * Builder class.
86 */
87 public static class Builder {
88
89 BeanStore parent;
90 boolean readOnly, threadSafe;
91 Object outer;
92 Class<? extends BeanStore> type;
93 BeanStore impl;
94
95 /**
96 * Constructor.
97 */
98 protected Builder() {}
99
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 }