diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java index 2b482764..9b14eb3f 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java @@ -12,7 +12,6 @@ import java.util.Collection; -import jakarta.enterprise.invoke.InvokerBuilder; import jakarta.enterprise.lang.model.AnnotationInfo; import jakarta.enterprise.lang.model.declarations.ClassInfo; import jakarta.enterprise.lang.model.declarations.FieldInfo; @@ -164,25 +163,6 @@ public interface BeanInfo { */ Collection injectionPoints(); - /** - * Returns a new {@link InvokerBuilder} for given method. The builder eventually produces - * an opaque representation of the invoker for the given method. - *

- * The {@code method} must be declared on the bean class or inherited from a supertype - * of the bean class of this bean, otherwise an exception is thrown. - *

- * If an invoker may not be obtained for given {@code method} as described - * in {@link jakarta.enterprise.invoke.Invoker Invoker}, an exception is thrown. - *

- * If this method is called outside the {@code @Registration} phase, an exception is thrown. - * - * @param method method of this bean, must not be {@code null} - * @return the invoker builder, never {@code null} - * @since 4.1 - */ - // TODO we may want to introduce another entrypoint for this operation - InvokerBuilder createInvoker(MethodInfo method); - // --- /** diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerFactory.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerFactory.java new file mode 100644 index 00000000..8ecbbe10 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerFactory.java @@ -0,0 +1,24 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.invoke.InvokerBuilder; +import jakarta.enterprise.lang.model.declarations.MethodInfo; + +/** + * Factory for {@link InvokerBuilder}s. + * + * @since 4.1 + */ +public interface InvokerFactory { + /** + * Returns a new {@link InvokerBuilder} for given method of given bean. The builder + * eventually produces an opaque representation of the generated invoker. + *

+ * If an invoker may not be built for given {@code bean} or for given {@code method}, + * an exception is thrown. + * + * @param bean target bean of the invoker, must not be {@code null} + * @param method target method of the invoker, must not be {@code null} + * @return the invoker builder, never {@code null} + */ + InvokerBuilder createInvoker(BeanInfo bean, MethodInfo method); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java index d0038c4a..5f4f28bf 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java @@ -11,12 +11,13 @@ package jakarta.enterprise.inject.build.compatible.spi; import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.invoke.InvokerBuilder; import jakarta.enterprise.lang.model.declarations.MethodInfo; /** - * Opaque token that stands in for an invoker registered using {@link BeanInfo#createInvoker(MethodInfo)}. - * It can only be used to materialize an {@link Invoker} in a synthetic component; see - * {@link SyntheticBeanBuilder#withParam(String, InvokerInfo) SyntheticBeanBuilder.withParam()} or + * Opaque token that stands in for an invoker registered using {@link InvokerFactory#createInvoker(BeanInfo, MethodInfo)} + * and {@link InvokerBuilder#build()}. It can only be used to materialize an {@link Invoker} in a synthetic component; + * see {@link SyntheticBeanBuilder#withParam(String, InvokerInfo) SyntheticBeanBuilder.withParam()} or * {@link SyntheticObserverBuilder#withParam(String, InvokerInfo) SyntheticObserverBuilder.withParam()}. * * @since 4.1 diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Registration.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Registration.java index 8870ad6b..2f128d70 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Registration.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Registration.java @@ -49,6 +49,7 @@ *

* Additionally, methods annotated {@code @Registration} may declare parameters of these types: *

diff --git a/api/src/main/java/jakarta/enterprise/invoke/Invoker.java b/api/src/main/java/jakarta/enterprise/invoke/Invoker.java index 52b8796b..1c6316a0 100644 --- a/api/src/main/java/jakarta/enterprise/invoke/Invoker.java +++ b/api/src/main/java/jakarta/enterprise/invoke/Invoker.java @@ -11,122 +11,55 @@ package jakarta.enterprise.invoke; /** - * Allows indirectly invoking a method that belongs to a managed bean (the target method). - * To invoke the method, the caller must provide all the arguments that the target method accepts, - * as well as the instance on which the target method is to be invoked, if it is not {@code static}. + * An invoker allows indirect invocation of its target method on an instance of its target + * bean. *

- * Whenever a direct invocation of a method is a business method invocation, an indirect invocation - * of that method through an invoker is also a business method invocation. + * CDI-based frameworks are expected to use invokers when they need to invoke application + * methods. Applications are not supposed to use invokers, as they can invoke their own + * methods directly. *

- * Invoker implementations must be thread-safe. It is possible to use a single invoker instance - * to perform multiple independent invocations of the target method, possibly on different instances - * and with different arguments. - * - *

Obtaining an invoker

- * - * The CDI container allows {@linkplain InvokerBuilder building} an invoker for non-private - * methods declared on a managed bean class or inherited from a supertype. Attempting to build - * an invoker for a private method or a constructor of a managed bean class leads to a deployment - * problem. Attempting to build an invoker for a method of a class that is not a managed bean class - * or that is an interceptor or decorator class leads to a deployment problem. - *

- * Multiple managed beans may inherit a method from a common supertype. In that case, each bean - * conceptually has its own method and an invoker obtained for one bean may not be used to invoke - * the method on the other bean. - *

- * Using the {@link InvokerBuilder} is the only way to obtain an invoker. An {@code InvokerBuilder} - * can only be obtained in CDI portable extensions and build compatible extensions. - * - *

Example

- * - * To illustrate how invokers work, let's take a look at an example. Say that the following bean - * exists and has a method that you want to invoke indirectly: + * For example, assume the following managed bean exists: * *
  * @Dependent
- * class MyService {
- *     String hello(String name) {
+ * public class MyService {
+ *     public String hello(String name) {
  *         return "Hello " + name + "!";
  *     }
  * }
  * 
* - * When you obtain an {@code InvokerBuilder} for the {@code hello()} method, you can - * immediately build a direct invoker. In a portable extension, this results in an invoker: - * - *
- * InvokerBuilder<Invoker<MyService, String>> builder = ...;
- * Invoker<MyService, String> invoker = builder.build();
- * 
- * - * In a build compatible extension, this results in an opaque token that later - * materializes as an invoker: + * Further, assume that {@code invoker} is an invoker for the {@code hello()} method + * of the {@code MyService} bean and {@code myService} is a contextual reference for the bean. + * Then, to invoke the {@code hello()} method indirectly, a framework would call * *
- * InvokerBuilder<InvokerInfo> builder = ...;
- * InvokerInfo invoker = builder.build();
+ * invoker.invoke(myService, new Object[] { "world" })
  * 
* - * To call the {@code hello()} method through this invoker, call - * {@code invoker.invoke(myService, new Object[] {"world"})}. - * The return value is {@code "Hello world!"}. - *

- * An implementation of the direct invoker above is equivalent to the following class: + * The return value would be {@code "Hello world!"}. * - *

- * class TheInvoker implements Invoker<MyService, String> {
- *     String invoke(MyService instance, Object[] arguments) {
- *         return instance.hello((String) arguments[0]);
- *     }
- * }
- * 
- * - * @param type of the target instance - * @param return type of the method + * @param type of the target bean + * @param return type of the target method * @since 4.1 * @see #invoke(Object, Object[]) */ public interface Invoker { /** - * Invokes the target method of this invoker on given {@code instance}, passing given - * {@code arguments}. If the target method is {@code static}, the {@code instance} is ignored; - * by convention, it should be {@code null}. If the target method returns normally, this - * method returns its return value, unless the target method is declared {@code void}, - * in which case this method returns {@code null}. If the target method throws an exception, - * this method rethrows it directly. - *

- * If some parameter of the target method declares a primitive type, the corresponding element of - * the {@code arguments} array must be of the corresponding wrapper type. No type conversions are - * performed, so if the parameter is declared {@code int}, the argument must be an {@code Integer} - * and may not be {@code Short} or {@code Long}. If the argument is {@code null}, the default value - * of the primitive type is used. Note that this does not apply to arrays of primitive types; - * if a parameter is declared {@code int[]}, the argument must be {@code int[]} and may not be - * {@code Integer[]}. - *

- * If the target method is not {@code static} and {@code instance} is {@code null}, - * a {@link NullPointerException} is thrown. If the target method is not {@code static} and - * the {@code instance} is not assignable to the class of the bean to which the method belongs, - * a {@link ClassCastException} is thrown. - *

- * If the target method declares no parameter, {@code arguments} are ignored. If the target method - * declares any parameter and {@code arguments} is {@code null}, {@link NullPointerException} is - * thrown. If the {@code arguments} array has fewer elements than the number of parameters of - * the target method, {@link ArrayIndexOutOfBoundsException} is thrown. If the {@code arguments} - * array has more elements than the number of parameters of the target method, the excess elements - * are ignored. If some of the {@code arguments} is not assignable to the declared type of - * the corresponding parameter of the target method, {@link ClassCastException} is thrown. - * - * TODO the previous 2 paragraphs refer to "assignability", which needs to be defined somewhere! - * - * TODO when the `InvokerBuilder` applies transformations, some of the requirements above - * are no longer strictly necessary, should reflect that in this text somehow (it is already - * mentioned in `InvokerBuilder`, but that likely isn't enough) + * Invokes the target method on the given {@code instance} of the target bean, passing + * given {@code arguments}. If the target method returns normally, this method returns + * its return value, unless the target method is declared {@code void}, in which case + * this method returns {@code null}. If the target method throws an exception, it is + * rethrown directly. * - * @param instance the instance on which the target method is to be invoked, may only be {@code null} - * if the method is {@code static} - * @param arguments arguments to be supplied to the target method, may only be {@code null} - * if the method declares no parameter - * @return return value of the target method, or {@code null} if the method is declared {@code void} + * @param instance the instance of the target bean on which the target method is to be invoked; + * may only be {@code null} if the target method is {@code static} + * @param arguments arguments to be passed to the target method; may only be {@code null} + * if the target method declares no parameter + * @return return value of the target method, or {@code null} if the target method + * is declared {@code void} + * @throws RuntimeException when {@code instance} or {@code arguments} are incorrect + * @throws Exception when the target method throws an exception */ - R invoke(T instance, Object[] arguments); // TODO throws Exception ? + R invoke(T instance, Object[] arguments) throws Exception; } diff --git a/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java index 8b625565..2212e521 100644 --- a/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java +++ b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java @@ -360,9 +360,8 @@ public interface InvokerBuilder { InvokerBuilder setInvocationWrapper(Class clazz, String methodName); /** - * Returns the built {@link Invoker} or some represention of it. Implementations are allowed - * but not required to reuse already built invokers for the same target method with the same - * configuration. + * Returns the built {@link Invoker} or some representation of it. Implementations are allowed + * but not required to reuse already built invokers when possible. * * @return the built invoker */ diff --git a/spec/src/main/asciidoc/cdi-spec.asciidoc b/spec/src/main/asciidoc/cdi-spec.asciidoc index 6f5be6c1..140fca50 100644 --- a/spec/src/main/asciidoc/cdi-spec.asciidoc +++ b/spec/src/main/asciidoc/cdi-spec.asciidoc @@ -54,6 +54,8 @@ include::core/interceptors.asciidoc[] include::core/events.asciidoc[] +include::core/invokers.asciidoc[] + include::core/beanmanager_lite.asciidoc[] include::core/spi_lite.asciidoc[] @@ -79,6 +81,8 @@ include::core/decorators.asciidoc[] include::core/events_full.asciidoc[] +include::core/invokers_full.asciidoc[] + include::core/spi_full.asciidoc[] include::core/packagingdeployment_full.asciidoc[] diff --git a/spec/src/main/asciidoc/core/invokers.asciidoc b/spec/src/main/asciidoc/core/invokers.asciidoc new file mode 100644 index 00000000..3d3e7dec --- /dev/null +++ b/spec/src/main/asciidoc/core/invokers.asciidoc @@ -0,0 +1,174 @@ +[[method_invokers]] +== Method invokers + +CDI-based frameworks often need to invoke application methods declared on managed beans. +Frameworks cannot invoke application methods directly, because they are not compiled against the application code. +However, during application deployment, frameworks may observe application methods through CDI extensions and build an `Invoker` for each relevant method. +The invokers can then be used at application runtime to invoke the methods indirectly. + +Method invokers are not supposed to be used by application code, as applications may invoke their own methods directly. + +[[building_invoker]] +=== Building an `Invoker` + +The CDI container allows building an `Invoker` for a method of an <> <>. +The method for which the invoker is built is called the _target method_ of the invoker, and the managed bean is called the _target bean_ of the invoker. + +Invalid target methods are: + +* private methods, +* constructors, +* methods declared on the `java.lang.Object` class, except for the `toString()` method, +* methods that are not declared on the bean class of the target bean or inherited from its supertypes. + +Attempting to build an invoker for an invalid target method leads to a deployment problem. + +Attempting to build an invoker for a non-static target method declared on a type that is not present in the set of bean types of the target bean leads to non-portable behavior. +When the target bean is normal scoped, attempting to build an invoker for a non-static target method declared on an <> bean type of the target bean leads to non-portable behavior. + +When the target bean is not a managed bean, attempting to build an invoker leads to a deployment problem. +When the target bean is an interceptor, attempting to build an invoker leads to a deployment problem. + +Multiple managed beans may inherit a method from a common supertype. +In that case, an invoker must be built for each target bean individually. +An invoker built for one target bean may not be used to invoke the target method on an instance of another target bean. + +The only way to build an invoker is using the `InvokerBuilder`. +An `InvokerBuilder` can only be obtained in CDI portable extensions and build compatible extensions. +See <> for more information. + +[[using_invoker]] +=== Using an `Invoker` + +The `Invoker` interface contains a single method: + +[source,java] +---- +public interface Invoker { + R invoke(T instance, Object[] arguments) throws Exception; +} +---- + +Calling `invoke()` invokes the target method on given `instance` of the target bean, passing given `arguments`, and propagates back the return value or thrown exception. +The `instance` and `arguments` may be contextual or non-contextual objects. + +A single invoker instance may be used to perform multiple invocations of the target method, possibly on different instances of the target bean, possibly with different arguments. +Invoker implementations must be thread-safe. +Whether concurrent invocations of the target method are safe depends on the implementation of the target bean and is not generally guaranteed. + +Whenever a direct invocation of a method on an object is a business method invocation, an indirect invocation of that method on that object through an invoker is also a business method invocation. + +==== Behavior of `invoke()` + +If the target method is `static`, the `instance` is ignored; by convention, it should be `null`. +If the target method is not `static` and `instance` is `null`, a `RuntimeException` is thrown. +If the target method is not `static` and the `instance` is not permissible for the target method, a `RuntimeException` is thrown. + +The `instance` is permissible for the target method when: + +* the `instance` is a contextual reference for the target bean and the bean type that declares the target method, or +* the `instance` is a contextual reference for the target bean (regardless of the bean type) and the target method is declared on an interface that is present in the set of bean types of the target bean (see <>), or +* the `instance` is a non-contextual object and the class of the `instance` declares the target method or inherits it from a supertype, or +* in other, non-portable (implementation defined) cases. + +Correspondence between given `arguments` and declared parameters of the target method is positional: the Nth element of the `arguments` array is passed as the Nth argument to the target method. +If the target method is a variable arity method, the last element of the `arguments` array corresponds to the variable arity parameter (and therefore must be an array). +When passing an argument to the method, the applicable method invocation conversion is performed. + +If the target method declares no parameter, `arguments` are ignored. +If the target method declares any parameter and `arguments` is `null`, `RuntimeException` is thrown. +If the `arguments` array has fewer elements than the number of parameters of the target method, `RuntimeException` is thrown. +If the `arguments` array has more elements than the number of parameters of the target method, the excess elements are ignored. +If a method invocation conversion does not exist from the class of some of the `arguments` (or from the null type if the argument is `null`) to the declared type of the corresponding parameter of the target method, `RuntimeException` is thrown. + +NOTE: The type checking and conversion rules are aligned with pre-existing mechanisms for indirect method invocations, the Java reflection API and the method handles API. + +When the declared type of a parameter of the target method is not a reifiable type, callers of `Invoker.invoke()` must ensure that the corresponding argument is constructed appropriately. +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. +Say that the following bean exists in an application and has a method that you, the framework author, want to invoke indirectly: + +[source,java] +---- +@Dependent +public class MyService { + public String hello(String name) { + return "Hello " + name + "!"; + } +} +---- + +In a CDI extension, you obtain an `InvokerBuilder` for the `hello()` method and use it to build an invoker. +In a portable extension (see <>), this results in an invoker which should be stored for later usage: + +[source,java] +---- +InvokerBuilder> builder = ...; +Invoker invoker = builder.build(); +---- + +In a build compatible extension (see <>), this results in an opaque token that materializes as an `Invoker` at application runtime: + +[source,java] +---- +InvokerBuilder builder = ...; +InvokerInfo invoker = builder.build(); +---- + +To call the `hello()` method through this invoker, assuming that `myService` is a contextual reference for the bean, call: + +[source,java] +---- +invoker.invoke(myService, new Object[] {"world"}) +---- + +The return value is `"Hello world!"`. + +An implementation of the invoker above is equivalent to the following class: + +[source,java] +---- +public class TheInvoker implements Invoker { + public String invoke(MyService instance, Object[] arguments) { + return instance.hello((String) arguments[0]); + } +} +---- + +[[invoker_builder]] +=== Using `InvokerBuilder` + +`InvokerBuilder` can be obtained in build compatible extensions from `InvokerFactory.createInvoker()`: + +[source,java] +---- +public interface InvokerFactory { + InvokerBuilder createInvoker(BeanInfo bean, MethodInfo method); +} +---- + +An `InvokerFactory` may be declared as a parameter of `@Registration` extension methods. + +The target bean of the created invoker is the bean represented by the `BeanInfo` object passed to `createInvoker()`. +The target method of the created invoker is the method represented by the `MethodInfo` object passed to `createInvoker()`. + +[source,java] +---- +public interface InvokerBuilder { + ... + + T build(); +} +---- + +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 diff --git a/spec/src/main/asciidoc/core/invokers_full.asciidoc b/spec/src/main/asciidoc/core/invokers_full.asciidoc new file mode 100644 index 00000000..4f6f2847 --- /dev/null +++ b/spec/src/main/asciidoc/core/invokers_full.asciidoc @@ -0,0 +1,30 @@ +[[method_invokers_full]] +== Method invokers in {cdi_full} + +[[building_invoker_full]] +=== Building an `Invoker` in {cdi_full} + +In addition to rules defined in <>, the following rules apply. + +When the target bean is a decorator, attempting to build an invoker leads to a deployment problem. + +[[invoker_builder_full]] +=== Using `InvokerBuilder` in {cdi_full} + +In addition to rules defined in <>, the following rules apply. + +`InvokerBuilder` can be obtained in portable extensions from `ProcessManagedBean.createInvoker()`: + +[source,java] +---- +public interface ProcessManagedBean extends ProcessBean { + ... + + InvokerBuilder> createInvoker(AnnotatedMethod method); +} +---- + +The target bean of the created invoker is the bean for which the `ProcessManagedBean` event was fired. +The target method of the created invoker is the method represented by the `AnnotatedMethod` object passed to `createInvoker()`. + +Calling `InvokerBuilder.build()` produces an `Invoker` which should be stored for usage at application runtime. diff --git a/spec/src/main/asciidoc/core/spi_full.asciidoc b/spec/src/main/asciidoc/core/spi_full.asciidoc index ff6d3b27..9ce0430c 100644 --- a/spec/src/main/asciidoc/core/spi_full.asciidoc +++ b/spec/src/main/asciidoc/core/spi_full.asciidoc @@ -1378,9 +1378,12 @@ The `Bean` may implement `Interceptor` or `Decorator`. public interface ProcessManagedBean extends ProcessBean { public AnnotatedType getAnnotatedBeanClass(); + public InvokerBuilder> createInvoker(AnnotatedMethod method); } ---- +The `createInvoker` method allows creating an `InvokerBuilder` for the processed bean and the given target method (see <>). + [source, java] ---- public interface ProcessProducerMethod diff --git a/spec/src/main/asciidoc/core/spi_lite.asciidoc b/spec/src/main/asciidoc/core/spi_lite.asciidoc index b57d2c1e..620f188b 100644 --- a/spec/src/main/asciidoc/core/spi_lite.asciidoc +++ b/spec/src/main/asciidoc/core/spi_lite.asciidoc @@ -306,9 +306,19 @@ When an extension method declares a parameter of type `ObserverInfo`, it will be Additionally, extension methods annotated `@Registration` may declare parameters of the following types: +* `InvokerFactory` * `Types` * `Messages` (see <>) +[source,java] +---- +public interface InvokerFactory { + InvokerBuilder createInvoker(BeanInfo bean, MethodInfo method); +} +---- + +The `InvokerFactory` interface allows creating an `InvokerBuilder` for given target bean and target method (see <>). + [[bce_synthesis]] === The `@Synthesis` phase @@ -348,11 +358,12 @@ The parameter map may contain values of the following types: * `String` * `Class` * `Enum` +* `Invoker` * any annotation type * array of any previously mentioned type -When defining the parameter map on `SyntheticBeanBuilder` or `SyntheticObserverBuilder`, it is possible to use `ClassInfo` or `AnnotationInfo` to define parameter values. -When such parameter is looked up from the parameter map in the synthetic bean creation/destruction function or the synthetic observer notification function, the value will be of type `Class` or the respective annotation type. +When defining the parameter map on `SyntheticBeanBuilder` or `SyntheticObserverBuilder`, it is possible to use `ClassInfo`, `InvokerInfo`, or `AnnotationInfo` to define parameter values. +When such parameter is looked up from the parameter map in the synthetic bean creation/destruction function or the synthetic observer notification function, the value will be of type `Class`, `Invoker`, or the respective annotation type. [[bce_validation]]