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 2aeb0a49..df54baab 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 @@ -10,6 +10,7 @@ package jakarta.enterprise.inject.build.compatible.spi; +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; @@ -162,6 +163,25 @@ 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/InvokerInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java new file mode 100644 index 00000000..d0038c4a --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InvokerInfo.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Red Hat and others + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.invoke.Invoker; +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 + * {@link SyntheticObserverBuilder#withParam(String, InvokerInfo) SyntheticObserverBuilder.withParam()}. + * + * @since 4.1 + */ +public interface InvokerInfo { +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Parameters.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Parameters.java index 54ad368d..96b28dc7 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Parameters.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Parameters.java @@ -10,6 +10,8 @@ package jakarta.enterprise.inject.build.compatible.spi; +import jakarta.enterprise.invoke.Invoker; + /** * A {@code String}-keyed parameter map. The parameter mappings are defined * by a synthetic component builder. The CDI container passes the parameter map @@ -40,13 +42,16 @@ *

* Annotation-typed parameters are available as instances of the annotation type, * even if an instance of {@code AnnotationInfo} was passed to the builder. + *

+ * Invoker-typed parameters are available as instances of {@link Invoker}, even though + * an instance of {@code InvokerInfo} was passed to the builder. */ public interface Parameters { /** * Returns the value of a parameter with given {@code key}. The value is expected to be of given {@code type}. * - * @param key the parameter key; must not be {@code null} - * @param type the parameter type; must not be {@code null} + * @param key the parameter key, must not be {@code null} + * @param type the parameter type, must not be {@code null} * @param the parameter type * @return the parameter value, or {@code null} if parameter with given {@code key} does not exist * @throws ClassCastException if the parameter exists, but is of a different type @@ -57,8 +62,8 @@ public interface Parameters { * Returns the value of a parameter with given {@code key}. The value is expected to be of given {@code type}. * If the parameter does not exist, returns {@code defaultValue}. * - * @param key the parameter key; must not be {@code null} - * @param type the parameter type; must not be {@code null} + * @param key the parameter key, must not be {@code null} + * @param type the parameter type, must not be {@code null} * @param defaultValue the value to return if parameter with given {@code key} does not exist * @param the parameter type * @return the parameter value, or {@code defaultValue} if parameter with given {@code key} does not exist diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java index 2f2b8f1e..8afb2c94 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java @@ -405,6 +405,36 @@ public interface SyntheticBeanBuilder { */ SyntheticBeanBuilder withParam(String key, Annotation[] value); + /** + * Adds an invoker-valued parameter to the parameter map. The parameter map is passed + * to the {@linkplain SyntheticBeanCreator creation} and {@linkplain SyntheticBeanDisposer destruction} + * functions when a bean instance is created/destroyed. + *

+ * When looked up from the parameter map in the creation/destruction function, the value will be + * an instance of {@link jakarta.enterprise.invoke.Invoker Invoker}, not an {@code InvokerInfo}. + * + * @param key the parameter key, must not be {@code null} + * @param value the parameter value + * @return this {@code SyntheticBeanBuilder} + * @since 4.1 + */ + SyntheticBeanBuilder withParam(String key, InvokerInfo value); + + /** + * Adds an invoker array-valued parameter to the parameter map. The parameter map is passed + * to the {@linkplain SyntheticBeanCreator creation} and {@linkplain SyntheticBeanDisposer destruction} + * functions when a bean instance is created/destroyed. + *

+ * When looked up from the parameter map in the creation/destruction function, the values will be + * instances of {@link jakarta.enterprise.invoke.Invoker Invoker}, not {@code InvokerInfo}. + * + * @param key the parameter key, must not be {@code null} + * @param value the parameter value + * @return this {@code SyntheticBeanBuilder} + * @since 4.1 + */ + SyntheticBeanBuilder withParam(String key, InvokerInfo[] value); + /** * Sets the class of the synthetic bean {@linkplain SyntheticBeanCreator creation} function. * CDI container will create an instance of the creation function every time when it needs diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java index 25fffcb7..4e2b102c 100644 --- a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java @@ -334,6 +334,34 @@ public interface SyntheticObserverBuilder { */ SyntheticObserverBuilder withParam(String key, Annotation[] value); + /** + * Adds an invoker-valued parameter to the parameter map. The parameter map is passed + * to the {@linkplain SyntheticObserver event notification} function when the event is fired. + *

+ * When looked up from the parameter map in the event notification function, the value will be + * an instance of {@link jakarta.enterprise.invoke.Invoker Invoker}, not an {@code InvokerInfo}. + * + * @param key the parameter key, must not be {@code null} + * @param value the parameter value + * @return this {@code SyntheticBeanBuilder} + * @since 4.1 + */ + SyntheticObserverBuilder withParam(String key, InvokerInfo value); + + /** + * Adds an invoker array-valued parameter to the parameter map. The parameter map is passed + * to the {@linkplain SyntheticObserver event notification} function when the event is fired. + *

+ * When looked up from the parameter map in the event notification function, the values will be + * instances of {@link jakarta.enterprise.invoke.Invoker Invoker}, not {@code InvokerInfo}. + * + * @param key the parameter key, must not be {@code null} + * @param value the parameter value + * @return this {@code SyntheticBeanBuilder} + * @since 4.1 + */ + SyntheticObserverBuilder withParam(String key, InvokerInfo[] value); + /** * Sets the class of the synthetic observer {@linkplain SyntheticObserver event notification} function. * CDI container will create an instance of the event notification function every time when it needs diff --git a/api/src/main/java/jakarta/enterprise/inject/spi/ProcessManagedBean.java b/api/src/main/java/jakarta/enterprise/inject/spi/ProcessManagedBean.java index f2cd0b21..9b10bd86 100644 --- a/api/src/main/java/jakarta/enterprise/inject/spi/ProcessManagedBean.java +++ b/api/src/main/java/jakarta/enterprise/inject/spi/ProcessManagedBean.java @@ -15,6 +15,9 @@ */ package jakarta.enterprise.inject.spi; +import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.invoke.InvokerBuilder; + /** *

* The container fires an event of this type for each enabled managed bean, before registering the @@ -38,4 +41,17 @@ public interface ProcessManagedBean extends ProcessBean { * @throws IllegalStateException if called outside of the observer method invocation */ public AnnotatedType getAnnotatedBeanClass(); + + /** + * Returns a new {@link InvokerBuilder} for given method. The builder eventually produces an 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 the bean being registered, otherwise an exception is thrown. + * + * @param method method of the bean being registered, must not be {@code null} + * @return the invoker builder, never {@code null} + * @since 4.1 + */ + public InvokerBuilder> createInvoker(AnnotatedMethod method); } diff --git a/api/src/main/java/jakarta/enterprise/invoke/Invoker.java b/api/src/main/java/jakarta/enterprise/invoke/Invoker.java new file mode 100644 index 00000000..2f06d6f3 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/invoke/Invoker.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 Red Hat and others + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +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}. + *

+ * 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. + *

+ * 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: + * + *
+ * @Dependent
+ * class MyService {
+ *     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: + * + *
+ * InvokerBuilder<InvokerInfo> builder = ...;
+ * InvokerInfo invoker = builder.build();
+ * 
+ * + * 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: + * + *

+ * 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 + * @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! + * + * @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} + */ + R invoke(T instance, Object[] arguments); // TODO throws Exception ? +} diff --git a/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java new file mode 100644 index 00000000..f8df0e84 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Red Hat and others + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package jakarta.enterprise.invoke; + +/** + * Builder of {@link Invoker}s. + * + * @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 + */ +public interface InvokerBuilder { + /** + * 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. + * + * @return the built invoker + */ + T build(); +} diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index 1aa23218..4239d4d3 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -10,6 +10,7 @@ exports jakarta.enterprise.inject.se; exports jakarta.enterprise.inject.spi; exports jakarta.enterprise.inject.spi.configurator; + exports jakarta.enterprise.invoke; exports jakarta.enterprise.util; requires transitive jakarta.annotation;