diff --git a/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java index 2212e521..41ccc4c3 100644 --- a/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java +++ b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java @@ -11,353 +11,68 @@ package jakarta.enterprise.invoke; /** - * Builder of {@link Invoker}s that allows configuring input lookups, input and output - * transformations, and invoker wrapping. The method for which the invoker is built is - * called the target method. If a lookup is configured, the corresponding input - * of the invoker is ignored and an instance is looked up from the CDI container before - * the target method is invoked. If a transformation is configured, the corresponding input - * or output of the invoker is modified in certain way before or after the target method - * is invoked. If a wrapper is configured, the invoker is passed to custom code for getting - * invoked. As a result, the built {@code Invoker} instance may have more complex behavior - * than just directly calling the target method. - *

- * Transformations and wrapping are expressed by ordinary methods that must have - * a pre-defined signature, as described below. Such methods are called - * transformers and wrappers. - *

- * Invokers may only be built during deployment. It is not possible to build new invokers - * at application runtime. + * Builder of {@link Invoker}s. Allows configuring additional behaviors on top of a plain + * method invocation. * - *

Example

+ *

Lookups

* - * Before describing in detail how lookups, transformers and wrappers work, let's take - * a look at an example. Say we have the following bean with a method: + * For the target bean instance ({@link #withInstanceLookup()}) and for each target method + * parameter ({@link #withArgumentLookup(int)}), it is possible to specify that the corresponding + * value passed to {@code Invoker.invoke()} shall be ignored and a value shall be looked up + * from the CDI container instead. + *

+ * For example, assume the following managed bean exists: * *

- * class MyService {
- *     String hello(String name) {
+ * @Dependent
+ * public class MyService {
+ *     public String hello(String name) {
  *         return "Hello " + name + "!";
  *     }
  * }
  * 
* - * And we want to build an invoker that looks up {@code MyService} from the CDI container, - * always passes the argument to {@code hello()} as all upper-case, and repeats the return - * value twice. To transform the argument, we can use the zero-parameter method - * {@code String.toUpperCase()}, and to transform the return value, we write a transformer - * as a simple {@code static} method: - * - *
- * class Transformations {
- *     static String repeatTwice(String str) {
- *         return str + " " + str;
- *     }
- * }
- * 
- * - * Then, assuming we have obtained the {@code InvokerBuilder} for {@code MyService.hello()}, - * we can set up the lookup and transformations and build an invoker like so: + * A CDI-based framework may want to build an invoker for the {@code hello()} method that + * automatically looks up {@code MyService} from the CDI container, instead of having to + * obtain a contextual reference manually. + *

+ * Assuming that {@code builder} is an {@code InvokerBuilder} for {@code MyService.hello()}, + * such invoker can be built: * *

- * builder.setInstanceLookup()
- *         .setArgumentTransformer(0, String.class, "toUpperCase")
- *         .setReturnValueTransformer(Transformations.class, "repeatTwice")
- *         .build();
+ * builder.withInstanceLookup().build();
  * 
* - * The resulting invoker will be equivalent to the following class: + * Later, to invoke the {@code hello()} method, a framework could pass {@code null} as the instance: * *
- * class TheInvoker implements Invoker<MyService, String> {
- *     String invoke(MyService ignored, Object[] arguments) {
- *         MyService instance = CDI.current().select(MyService.class).get();
- *         String argument = (String) arguments[0];
- *         String transformedArgument = argument.toUpperCase();
- *         String result = instance.hello(transformedArgument);
- *         String transformedResult = Transformations.repeatTwice(result);
- *         return transformedResult;
- *     }
- * }
+ * invoker.invoke(null, new Object[] { "world" })
  * 
* - * The caller of this invoker may pass {@code null} as the target instance, because - * the invoker will lookup the target instance on its own. Therefore, calling - * {@code invoker.invoke(null, new Object[] {"world"})} will return - * {@code "Hello WORLD! Hello WORLD!"}. - * - *

General requirements

- * - * To refer to a transformer or a wrapper, all methods in this builder accept: - * 1. the {@code Class} that that declares the method, and 2. the {@code String} name - * of the method. - *

- * Transformers may be {@code static}, in which case they must be declared directly - * on the given class, or they may be instance methods, in which case they may be declared - * on the given class or inherited from any of its supertypes. - *

- * It is possible to register only one transformer of each kind, or for each argument - * position in case of argument transformers. Attempting to register a second transformer - * of the same kind, or for the same argument position, leads to an exception. - *

- * Wrappers must be {@code static} and must be declared directly on the given class. - * It is possible to register only one wrapper. Attempting to register a second wrapper - * leads to an exception. - *

- * It is a deployment problem if no method with given name and valid signature is found, - * or if multiple methods with given name and different valid signatures are found. It is - * a deployment problem if a registered transformer or wrapper is not {@code public}. - *

- * Transformers and wrappers may declare the {@code throws} clause. The declared exception - * types are ignored when searching for the method. - *

- * For the purpose of the specification of transformers and wrappers below, the term - * any-type is recursively defined as: the {@code java.lang.Object} class type, - * or a type variable that has no bound, or a type variable whose first bound is - * any-type. - * - *

Input lookups

- * - * For the target instance and for each argument, it is possible to specify that the value - * passed to {@code Invoker.invoke()} should be ignored and a value should be looked up - * from the CDI container instead. - *

- * For the target instance, a CDI lookup is performed with the required type equal to the bean - * class of the bean to which the target method belongs, and required qualifiers equal to the set - * of all qualifier annotations present on the bean class of the bean to which the target method - * belongs. When the target method is {@code static}, the target instance lookup is skipped. - *

- * For an argument, a CDI lookup is performed with the required type equal to the type of - * the corresponding parameter of the target method, and required qualifiers equal to the set - * of all qualifier annotations present on the corresponding parameter of the target method. - *

- * Implementations are required to resolve all lookups during deployment. It is a deployment - * problem if the lookup ends up unresolved or ambiguous. - *

- * If the looked up bean is {@code @Dependent}, it is guaranteed that the instance will be - * destroyed after the target method is invoked but before the the invoker returns. The order - * in which the looked up {@code @Dependent} beans are destroyed is not specified. - *

- * The order in which input lookups are performed in not specified and must not be relied upon. - * - *

Input transformations

- * - * The target method has 2 kinds of inputs: the target instance (unless the target method is - * {@code static}, in which case the target instance is ignored and should be {@code null} - * by convention) and arguments. These inputs correspond to the parameters of - * {@link Invoker#invoke(Object, Object[]) Invoker.invoke()}. - *

- * Each input can be transformed by a transformer that has one of the following signatures, - * where {@code X} and {@code Y} are types: - * - *

- * - * An input transformer must produce a type that can be consumed by the target method. - * Specifically: when {@code X} is any-type, it is not type checked during deployment. - * Otherwise, it is a deployment problem if {@code X} is not assignable to the corresponding type - * in the declaration of the target method (that is the bean class in case of target instance - * transformers, or the corresponding parameter type in case of argument transformers). {@code Y} - * is not type checked during deployment, so that input transformers may consume arbitrary types. - * TODO this paragraph refers to "assignability", which needs to be defined somewhere! - *

- * When a transformer is registered for given input, it is called before the target method is - * invoked, and the outcome of the transformer is used in the invocation instead of the original - * value passed to the invoker by its caller. - *

- * If the transformer declares the {@code Consumer} parameter, and the execution - * of the transformer calls {@code Consumer.accept()} with some {@code Runnable}, it is - * guaranteed that the {@code Runnable} will be called after the target method is invoked but - * before the invoker returns. These {@code Runnable}s are called cleanup tasks. - * The order of cleanup task execution is not specified. Passing a {@code null} cleanup task - * to the {@code Consumer} is permitted, but has no effect. - *

- * If an input transformation is configured for an input for which a lookup is also configured, - * the lookup is performed first and the transformation is applied to the looked up value. - * If the looked up bean for some input is {@code @Dependent}, it is guaranteed that all - * cleanup tasks registered by a transformer for that input are called before that looked up - * {@code @Dependent} bean is destroyed. - *

- * The order in which input transformations are performed in not specified and must not - * be relied upon. - * - *

Output transformations

- * - * The target method has 2 kinds of outputs: the return value and the thrown exception. These - * outputs correspond to the return value of {@link Invoker#invoke(Object, Object[]) Invoker.invoke()} - * or its thrown exception, respectively. - *

- * Each output can be transformed by a transformer that has one of the following signatures, - * where {@code X} and {@code Y} are types: - * - *

- * - * An output transformer must consume a type that can be produced by the target method. - * Specifically: when {@code Y} is any-type, it is not type checked during deployment. - * Otherwise, it is a deployment problem if {@code Y} is not assignable from the return type of - * the target method in case of return value transformers, or from {@code java.lang.Throwable} - * in case of exception transformers. {@code X} is not type checked during deployment, so that - * output transformers may produce arbitrary types. - * TODO this paragraph refers to "assignability", which needs to be defined somewhere! - *

- * When a transformer is registered for given output, it is called after the target method - * is invoked, and the outcome of the transformer is passed back to the caller of the invoker - * instead of the original output produced by the target method. - *

- * If the target method returns normally, any registered exception transformer is ignored; only - * the return value transformer is called. The return value transformer may throw, in which case - * the invoker will rethrow the exception. If the invoker is supposed to return normally, - * the return value transformer must return normally. - *

- * Similarly, if the target method throws, any registered return value transformer is ignored; - * only the exception transformer is called. The exception transformer may return normally, - * in which case the invoker will return the return value of the exception transformer. If - * the invoker is supposed to throw an exception, the exception transformer must throw. - * TODO this requires that implementations catch java.lang.Throwable, which is perhaps a bit too much? - * maybe stick with java.lang.Exception? - * - *

Invoker wrapping

- * - * An invoker, possibly utilizing input lookups and input/output transformations, may be wrapped - * by a custom piece of code for maximum flexibility. A wrapper must have the following signature, - * where {@code X}, {@code Y} and {@code Z} are types: - * - * - * - * A wrapper must operate on a matching instance type. Specifically: when {@code X} is - * any-type, it is not type checked during deployment. Otherwise, it is a deployment - * problem if {@code X} is not assignable from the class type of the bean class to which - * the target method belongs. {@code Y} and {@code Z} are not type checked during deployment. - *

- * When a wrapper is registered, 2 invokers for the same method are created. The inner - * invoker applies all lookups and transformations, as described in previous sections, and - * invokes the target method. The outer invoker calls the wrapper with the passed - * instance and arguments and an instance of the inner invoker. The outer invoker is returned - * by this invoker builder. - *

- * In other words, the outer invoker is equivalent to the following class: - * - *

- * class InvokerWrapper implements Invoker<X, Z> {
- *     Z invoke(X instance, Object[] arguments) {
- *         // obtain the invoker as if no wrapper existed
- *         Invoker<X, Y> invoker = obtainInvoker();
- *         return SomeClass.wrap(instance, arguments, invoker);
- *     }
- * }
- * 
- * - * If the wrapper returns normally, the outer invoker returns its return value, unless the wrapper - * is declared {@code void}, in which case the outer invoker returns {@code null}. If the wrapper - * throws an exception, the outer invoker rethrows it directly. - *

- * The wrapper is supposed to call the invoker it is passed, but does not necessarily have to. - * The wrapper may call the invoker multiple times. The wrapper must not use the invoker - * in any other way; specifically, it is forbidden to store the invoker instance anywhere - * or pass it to other methods that do not follow these rules. Doing so leads to non-portable - * behavior. - * - *

Type checking

- * - * An invoker created by this builder has relaxed type checking rules, when compared to - * the description in {@link Invoker#invoke(Object, Object[]) Invoker.invoke()}, depending - * on configured lookups, transformers and wrapper. Some types are checked during - * deployment, as described in previous sections. Other types are checked during invocation, - * at the very least due to the type checks performed implicitly by the JVM. The lookups, - * transformers and the wrapper must arrange the inputs and outputs so that when the method - * is eventually invoked, the rules described in - * {@link Invoker#invoke(Object, Object[]) Invoker.invoke()} all hold. - *

- * TODO specify what happens when a transformer/wrapper declares a parameter of a primitive type - * but the actual value passed to the invoker is `null` (the transformer should get a zero value?) - * TODO specify what happens when a transformer/wrapper declares a parameter of some type - * but the actual value passed to the invoker is not assignable to it (CCE?) + * The invoker would look up the instance of the target bean automatically, so the method would be + * invoked correctly and the return value would be {@code "Hello world!"}. * * @param type of outcome of this builder; always represents an {@code Invoker}, * but does not necessarily have to be an {@code Invoker} instance directly * @since 4.1 */ -// TODO more kinds of transformations could be defined, expecially for argument handling -// TODO it would be possible to specify a sequence of transformations for each input/output, instead of just one public interface InvokerBuilder { /** - * Enables lookup of the target instance. + * Enables lookup of the target bean instance. * * @return this builder */ - InvokerBuilder setInstanceLookup(); + InvokerBuilder withInstanceLookup(); /** * Enables lookup of the argument on given {@code position}. * * @param position zero-based argument position for which lookup is enabled * @return this builder - * @throws IllegalArgumentException if {@code position} is greather than or equal to - * the number of parameters declared by the target method - */ - InvokerBuilder setArgumentLookup(int position); - - /** - * Configures an input transformer for the target instance. - * - * @param clazz class that declares the transformer - * @param methodName transformer method name - * @return this builder - * @throws IllegalStateException if this method is called more than once - */ - InvokerBuilder setInstanceTransformer(Class clazz, String methodName); - - /** - * Configures an input transformer for the argument on given {@code position}. - * - * @param position zero-based argument position for which the input transformer is configured - * @param clazz class that declares the transformer - * @param methodName transformer method name - * @return this builder - * @throws IllegalArgumentException if {@code position} is greather than or equal to - * the number of parameters declared by the target method - * @throws IllegalStateException if this method is called more than once with the same {@code position} - */ - InvokerBuilder setArgumentTransformer(int position, Class clazz, String methodName); - - /** - * Configures an output transformer for the return value. - * - * @param clazz class that declares the transformer - * @param methodName transformer method name - * @return this builder - * @throws IllegalStateException if this method is called more than once - */ - InvokerBuilder setReturnValueTransformer(Class clazz, String methodName); - - /** - * Configures an output transformer for the thrown exception. - * - * @param clazz class that declares the transformer - * @param methodName transformer method name - * @return this builder - * @throws IllegalStateException if this method is called more than once - */ - InvokerBuilder setExceptionTransformer(Class clazz, String methodName); - - /** - * Configures an invoker wrapper. - * - * @param clazz class that declares the invoker wrapper - * @param methodName invoker wrapper method name - * @return this builder - * @throws IllegalStateException if this method is called more than once + * @throws IllegalArgumentException if {@code position} is less than 0 or greater than + * or equal to the number of parameters declared by the target method */ - InvokerBuilder setInvocationWrapper(Class clazz, String methodName); + InvokerBuilder withArgumentLookup(int position); /** * Returns the built {@link Invoker} or some representation of it. Implementations are allowed diff --git a/spec/src/main/asciidoc/core/invokers.asciidoc b/spec/src/main/asciidoc/core/invokers.asciidoc index 3d3e7dec..4f45b2b6 100644 --- a/spec/src/main/asciidoc/core/invokers.asciidoc +++ b/spec/src/main/asciidoc/core/invokers.asciidoc @@ -89,8 +89,6 @@ Otherwise, runtime failures are likely to occur. If the target method returns normally, its return value is returned (after boxing conversion if the target method's return type is a primitive type), unless the target method is declared `void`, in which case `null` is returned. If the target method throws an exception, it is rethrown directly. -// TODO when the `InvokerBuilder` applies transformations, some of the requirements above are no longer strictly necessary, we should reflect that in this text somehow - ==== Example To illustrate how method invokers work, let's take a look at an example. @@ -132,7 +130,7 @@ invoker.invoke(myService, new Object[] {"world"}) The return value is `"Hello world!"`. -An implementation of the invoker above is equivalent to the following class: +Internally, the container will create an implementation of the invoker, equivalent to the following class: [source,java] ---- @@ -171,4 +169,36 @@ public interface InvokerBuilder { Calling `InvokerBuilder.build()` produces an opaque token (`InvokerInfo`) that can be passed as a parameter to a `SyntheticBeanBuilder` or `SyntheticObserverBuilder` and materializes as an `Invoker` at application runtime. -// TODO lookups, transformers, wrappers +[[invoker_lookups]] +==== Configuring invoker lookups + +The `InvokerBuilder` allows configuring that the `instance` or any of the `arguments` passed to `Invoker.invoke()` should be ignored and a value should be looked up from the CDI container instead. + +[source,java] +---- +public interface InvokerBuilder { + InvokerBuilder withInstanceLookup(); + InvokerBuilder withArgumentLookup(int position); +} +---- + +When `withInstanceLookup()` is called on an invoker builder and the target method is not `static`, the `invoke()` method of the built invoker shall ignore the `instance` argument and instead obtain and use a contextual reference for the target bean and the bean type that declares the target method. +Calling `withInstanceLookup()` on an invoker builder for a `static` target method has no effect. + +When `withArgumentLookup()` is called on an invoker builder, the `invoke()` method of the built invoker shall ignore the given element of the `arguments` array and instead obtain and use an injectable reference for an injection point that is the corresponding parameter of the target method. +Calling `withArgumentLookup()` with `position` less than 0 or greater than or equal to the number of parameters of the target method leads to an `IllegalArgumentException`. + +In the following paragraphs, the beans whose instances shall be obtained by `Invoker.invoke()` as a result of calling `withInstanceLookup()` and `withArgumentLookup()` are called _looked up beans_. + +During deployment validation, implementations are required to identify all looked up beans for all built invokers, according to the rules of typesafe resolution, as defined in <>, resolving ambiguities according to <>. +It is a deployment problem if an attempt to identify a looked up bean results in an unsatisfied dependency or an ambiguous dependency that is not resolvable. + +All instances of `@Dependent` looked up beans obtained during `Invoker.invoke()` are destroyed before the `invoke()` method returns or throws. +The order in which the instances of `@Dependent` looked up beans are destroyed is not specified. + +The order in which instances of looked up beans are obtained during `Invoker.invoke()` in not specified. + +If an exception is thrown when creating an instance of a looked up bean during `Invoker.invoke()`, the exception is rethrown. + +NOTE: Destroying an instance is not permitted to throw an exception. +See <> for more information. diff --git a/spec/src/main/asciidoc/core/packagingdeployment.asciidoc b/spec/src/main/asciidoc/core/packagingdeployment.asciidoc index a68f88fe..dd6a4ede 100644 --- a/spec/src/main/asciidoc/core/packagingdeployment.asciidoc +++ b/spec/src/main/asciidoc/core/packagingdeployment.asciidoc @@ -72,7 +72,7 @@ At deployment time, the container performs the following steps: As part of that, the container must execute the <> and <> phases of build compatible extensions. * Next, the container must perform bean discovery, as defined in <>. As part of that, the container must execute the <> and <> phases of build compatible extensions. -* Finally, the container must detect deployment problems by validating bean dependencies. +* Finally, the container must detect deployment problems by validating bean dependencies and <>. As part of that, the container must execute the <> phase of build compatible extensions. At any step, the container must abort deployment if any definition errors or deployment problems exist, as defined in <>. diff --git a/spec/src/main/asciidoc/core/packagingdeployment_full.asciidoc b/spec/src/main/asciidoc/core/packagingdeployment_full.asciidoc index 3b679b46..fcb7c167 100644 --- a/spec/src/main/asciidoc/core/packagingdeployment_full.asciidoc +++ b/spec/src/main/asciidoc/core/packagingdeployment_full.asciidoc @@ -77,7 +77,7 @@ When an application is started, the container performs the following steps: * Next, the container must fire an event of type `AfterTypeDiscovery`, as defined in <>. * Next, the container must perform bean discovery, as defined in <>. * Next, the container must fire an event of type `AfterBeanDiscovery`, as defined in <>, and abort initialization of the application if any observer registers a definition error. -* Next, the container must detect deployment problems by validating bean dependencies and specialization and abort initialization of the application if any deployment problems exist, as defined in <>. +* Next, the container must detect deployment problems by validating bean dependencies, specialization and <> and abort initialization of the application if any deployment problems exist, as defined in <>. * Next, the container must fire an event of type `AfterDeploymentValidation`, as defined in <>, and abort initialization of the application if any observer registers a deployment problem. * Finally, the container begins directing requests to the application.