001package io.avaje.inject;
002
003import io.avaje.inject.spi.Module;
004import io.avaje.lang.NonNullApi;
005import io.avaje.lang.Nullable;
006import javax.inject.Provider;
007
008import java.lang.reflect.Type;
009import java.util.function.Consumer;
010
011/**
012 * Build a bean scope with options for shutdown hook and supplying external dependencies.
013 * <p>
014 * We can provide external dependencies that are then used in wiring the components.
015 * </p>
016 *
017 * <pre>{@code
018 *
019 *   // external dependencies
020 *   Pump pump = ...
021 *
022 *   BeanScope scope = BeanScope.builder()
023 *     .bean(pump)
024 *     .build();
025 *
026 *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
027 *   coffeeMaker.makeIt();
028 *
029 * }</pre>
030 */
031@NonNullApi
032public interface BeanScopeBuilder {
033
034  /**
035   * Create the bean scope registering a shutdown hook (defaults to false, no shutdown hook).
036   * <p>
037   * With {@code withShutdownHook(true)} a shutdown hook will be registered with the Runtime
038   * and executed when the JVM initiates a shutdown. This then will run the {@code preDestroy}
039   * lifecycle methods.
040   * </p>
041   * <pre>{@code
042   *
043   *   // automatically closed via try with resources
044   *
045   *   BeanScope scope = BeanScope.builder()
046   *     .shutdownHook(true)
047   *     .build());
048   *
049   *   // on JVM shutdown the preDestroy lifecycle methods are executed
050   *
051   * }</pre>
052   *
053   * @return This BeanScopeBuilder
054   */
055  BeanScopeBuilder shutdownHook(boolean shutdownHook);
056
057  /**
058   * Deprecated - migrate to shutdownHook().
059   */
060  @Deprecated
061  default BeanScopeBuilder withShutdownHook(boolean shutdownHook) {
062    return shutdownHook(shutdownHook);
063  }
064
065  /**
066   * Specify the modules to include in dependency injection.
067   * <p>
068   * Only beans related to the module are included in the BeanScope that is built.
069   * <p>
070   * When we do not explicitly specify modules then all modules that are not "custom scoped"
071   * are found and used via service loading.
072   *
073   * <pre>{@code
074   *
075   *   BeanScope scope = BeanScope.builder()
076   *     .modules(new CustomModule())
077   *     .build());
078   *
079   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
080   *   coffeeMaker.makeIt();
081   *
082   * }</pre>
083   *
084   * @param modules The modules that we want to include in dependency injection.
085   * @return This BeanScopeBuilder
086   */
087  BeanScopeBuilder modules(Module... modules);
088
089  /**
090   * Deprecated - migrate to modules()
091   */
092  @Deprecated
093  default BeanScopeBuilder withModules(Module... modules) {
094    return modules(modules);
095  }
096
097  /**
098   * Supply a bean to the scope that will be used instead of any
099   * similar bean in the scope.
100   * <p>
101   * This is typically expected to be used in tests and the bean
102   * supplied is typically a test double or mock.
103   * </p>
104   *
105   * <pre>{@code
106   *
107   *   // external dependencies
108   *   Pump pump = ...
109   *   Grinder grinder = ...
110   *
111   *   BeanScope scope = BeanScope.builder()
112   *     .beans(pump, grinder)
113   *     .build();
114   *
115   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
116   *   coffeeMaker.makeIt();
117   *
118   * }</pre>
119   *
120   * @param beans Externally provided beans used when injecting a dependency
121   *              for the bean or the interface(s) it implements
122   * @return This BeanScopeBuilder
123   */
124  BeanScopeBuilder beans(Object... beans);
125
126  /**
127   * Deprecated - migrate to beans().
128   */
129  @Deprecated
130  default BeanScopeBuilder withBeans(Object... beans) {
131    return beans(beans);
132  }
133
134  /**
135   * Add a supplied bean instance with the given injection type (typically an interface type).
136   *
137   * <pre>{@code
138   *
139   *   Pump externalDependency = ...
140   *
141   *   try (BeanScope scope = BeanScope.builder()
142   *     .bean(Pump.class, externalDependency)
143   *     .build()) {
144   *
145   *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
146   *     coffeeMaker.makeIt();
147   *
148   *     Pump pump = scope.get(Pump.class);
149   *     assertThat(pump).isSameAs(externalDependency);
150   *   }
151   *
152   * }</pre>
153   *
154   * @param type The dependency injection type this bean is target for
155   * @param bean The supplied bean instance to use for injection
156   */
157  <D> BeanScopeBuilder bean(Class<D> type, D bean);
158
159  /**
160   * Deprecated - migrate to bean().
161   */
162  @Deprecated
163  default <D> BeanScopeBuilder withBean(Class<D> type, D bean) {
164    return bean(type, bean);
165  }
166
167  /**
168   * Add a supplied bean instance with the given name and injection type.
169   *
170   * @param name The name qualifier
171   * @param type The dependency injection type this bean is target for
172   * @param bean The supplied bean instance to use for injection
173   */
174  <D> BeanScopeBuilder bean(String name, Class<D> type, D bean);
175
176  /**
177   * Deprecated - migrate to bean().
178   */
179  @Deprecated
180  default <D> BeanScopeBuilder withBean(String name, Class<D> type, D bean) {
181    return bean(name, type, bean);
182  }
183
184  /**
185   * Add a supplied bean instance with the given name and generic type.
186   *
187   * @param name The name qualifier
188   * @param type The dependency injection type this bean is target for
189   * @param bean The supplied bean instance to use for injection
190   */
191  <D> BeanScopeBuilder bean(String name, Type type, D bean);
192
193  /**
194   * Deprecated - migrate to bean().
195   */
196  @Deprecated
197  default <D> BeanScopeBuilder withBean(String name, Type type, D bean) {
198    return bean(name, type, bean);
199  }
200
201  /**
202   * Add a supplied bean instance with a generic type.
203   *
204   * @param type The dependency injection type this bean is target for
205   * @param bean The supplied bean instance to use for injection
206   */
207  <D> BeanScopeBuilder bean(Type type, D bean);
208
209  /**
210   * Add a supplied bean provider that acts as a default fallback for a dependency.
211   * <p>
212   * This provider is only called if nothing else provides the dependency. It effectively
213   * uses `@Secondary` priority.
214   *
215   * @param type     The type of the dependency
216   * @param provider The provider of the dependency.
217   */
218  default <D> BeanScopeBuilder provideDefault(Type type, Provider<D> provider) {
219    return provideDefault(null, type, provider);
220  }
221
222  /**
223   * Add a supplied bean provider that acts as a default fallback for a dependency.
224   * <p>
225   * This provider is only called if nothing else provides the dependency. It effectively
226   * uses `@Secondary` priority.
227   *
228   * @param name     The name qualifier
229   * @param type     The type of the dependency
230   * @param provider The provider of the dependency.
231   */
232  <D> BeanScopeBuilder provideDefault(@Nullable String name, Type type, Provider<D> provider);
233
234  /**
235   * Deprecated - migrate to bean().
236   */
237  @Deprecated
238  default <D> BeanScopeBuilder withBean(Type type, D bean) {
239    return bean(type, bean);
240  }
241
242  /**
243   * Set the ClassLoader to use when loading modules.
244   *
245   * @param classLoader The ClassLoader to use
246   */
247  BeanScopeBuilder classLoader(ClassLoader classLoader);
248
249  /**
250   * Use the given BeanScope as the parent. This becomes an additional
251   * source of beans that can be wired and accessed in this scope.
252   *
253   * @param parent The BeanScope that acts as the parent
254   */
255  BeanScopeBuilder parent(BeanScope parent);
256
257  /**
258   * Deprecated - migrate to parent().
259   */
260  @Deprecated
261  default BeanScopeBuilder withParent(BeanScope parent) {
262    return parent(parent);
263  }
264
265  /**
266   * Use the given BeanScope as the parent additionally specifying if beans
267   * added will effectively override beans that exist in the parent scope.
268   * <p>
269   * By default, child scopes will override a bean that exists in a parent scope.
270   * For testing purposes, parentOverride=false is used such that bean provided
271   * in parent test scopes are used (unless we mock() or spy() them).
272   * <p>
273   * See TestBeanScope in avaje-inject-test which has helper methods to build
274   * BeanScopes for testing with the "Global test scope" as a parent scope.
275   *
276   * @param parent         The BeanScope that acts as the parent
277   * @param parentOverride When false do not add beans that already exist on the parent.
278   *                       When true add beans regardless of whether they exist in the parent scope.
279   */
280  BeanScopeBuilder parent(BeanScope parent, boolean parentOverride);
281
282  /**
283   * Deprecated - migrate to parent().
284   */
285  @Deprecated
286  default BeanScopeBuilder withParent(BeanScope parent, boolean parentOverride) {
287    return parent(parent, parentOverride);
288  }
289
290  /**
291   * Extend the builder to support testing using mockito with
292   * <code>withMock()</code> and <code>withSpy()</code> methods.
293   *
294   * @return The builder with extra testing support for mockito mocks and spies
295   */
296  BeanScopeBuilder.ForTesting forTesting();
297
298  /**
299   * Build and return the bean scope.
300   * <p>
301   * The BeanScope is effectively immutable in that all components are created
302   * and all PostConstruct lifecycle methods have been invoked.
303   * <p>
304   * The beanScope effectively contains eager singletons.
305   *
306   * @return The BeanScope
307   */
308  BeanScope build();
309
310  /**
311   * Extends the building with testing specific support for mocks and spies.
312   */
313  interface ForTesting extends BeanScopeBuilder {
314
315    /**
316     * Use a mockito mock when injecting this bean type.
317     *
318     * <pre>{@code
319     *
320     *   try (BeanScope scope = BeanScope.builder()
321     *     .forTesting()
322     *     .mock(Pump.class)
323     *     .mock(Grinder.class)
324     *     .build()) {
325     *
326     *
327     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
328     *     coffeeMaker.makeIt();
329     *
330     *     // this is a mockito mock
331     *     Grinder grinder = scope.get(Grinder.class);
332     *     verify(grinder).grindBeans();
333     *   }
334     *
335     * }</pre>
336     */
337    BeanScopeBuilder.ForTesting mock(Class<?> type);
338
339    /**
340     * Deprecated - migrate to mock().
341     */
342    @Deprecated
343    default BeanScopeBuilder.ForTesting withMock(Class<?> type) {
344      return mock(type);
345    }
346
347    /**
348     * Register as a Mockito mock with a qualifier name.
349     *
350     * <pre>{@code
351     *
352     *   try (BeanScope scope = BeanScope.builder()
353     *     .forTesting()
354     *     .mock(Store.class, "red")
355     *     .mock(Store.class, "blue")
356     *     .build()) {
357     *
358     *     ...
359     *   }
360     *
361     * }</pre>
362     */
363    BeanScopeBuilder.ForTesting mock(Class<?> type, String name);
364
365    /**
366     * Deprecated - migrate to mock().
367     */
368    @Deprecated
369    default BeanScopeBuilder.ForTesting withMock(Class<?> type, String name) {
370      return mock(type, name);
371    }
372
373    /**
374     * Use a mockito mock when injecting this bean type additionally
375     * running setup on the mock instance.
376     *
377     * <pre>{@code
378     *
379     *   try (BeanScope scope = BeanScope.builder()
380     *     .forTesting()
381     *     .mock(Pump.class)
382     *     .mock(Grinder.class, grinder -> {
383     *
384     *       // setup the mock
385     *       when(grinder.grindBeans()).thenReturn("stub response");
386     *     })
387     *     .build()) {
388     *
389     *
390     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
391     *     coffeeMaker.makeIt();
392     *
393     *     // this is a mockito mock
394     *     Grinder grinder = scope.get(Grinder.class);
395     *     verify(grinder).grindBeans();
396     *   }
397     *
398     * }</pre>
399     */
400    <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer);
401
402    /**
403     * Deprecated - migrate to mock().
404     */
405    @Deprecated
406    default <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer) {
407      return mock(type, consumer);
408    }
409
410    /**
411     * Use a mockito spy when injecting this bean type.
412     *
413     * <pre>{@code
414     *
415     *   try (BeanScope scope = BeanScope.builder()
416     *     .forTesting()
417     *     .spy(Pump.class)
418     *     .build()) {
419     *
420     *     // setup spy here ...
421     *     Pump pump = scope.get(Pump.class);
422     *     doNothing().when(pump).pumpSteam();
423     *
424     *     // act
425     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
426     *     coffeeMaker.makeIt();
427     *
428     *     verify(pump).pumpWater();
429     *     verify(pump).pumpSteam();
430     *   }
431     *
432     * }</pre>
433     */
434    BeanScopeBuilder.ForTesting spy(Class<?> type);
435
436    /**
437     * Deprecated - migrate to spy().
438     */
439    @Deprecated
440    default BeanScopeBuilder.ForTesting withSpy(Class<?> type) {
441      return spy(type);
442    }
443
444    /**
445     * Register a Mockito spy with a qualifier name.
446     *
447     * <pre>{@code
448     *
449     *   try (BeanScope scope = BeanScope.builder()
450     *     .forTesting()
451     *     .spy(Store.class, "red")
452     *     .spy(Store.class, "blue")
453     *     .build()) {
454     *
455     *     ...
456     *   }
457     *
458     * }</pre>
459     */
460    BeanScopeBuilder.ForTesting spy(Class<?> type, String name);
461
462    /**
463     * Deprecated - migrate to spy().
464     */
465    @Deprecated
466    default BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name) {
467      return spy(type, name);
468    }
469
470    /**
471     * Use a mockito spy when injecting this bean type additionally
472     * running setup on the spy instance.
473     *
474     * <pre>{@code
475     *
476     *   try (BeanScope scope = BeanScope.builder()
477     *     .forTesting()
478     *     .spy(Pump.class, pump -> {
479     *       // setup the spy
480     *       doNothing().when(pump).pumpWater();
481     *     })
482     *     .build()) {
483     *
484     *     // or setup here ...
485     *     Pump pump = scope.get(Pump.class);
486     *     doNothing().when(pump).pumpSteam();
487     *
488     *     // act
489     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
490     *     coffeeMaker.makeIt();
491     *
492     *     verify(pump).pumpWater();
493     *     verify(pump).pumpSteam();
494     *   }
495     *
496     * }</pre>
497     */
498    <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer);
499
500    /**
501     * Deprecated - migrate to spy().
502     */
503    @Deprecated
504    default <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer) {
505      return spy(type, consumer);
506    }
507
508  }
509}