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