Skip to content

Commit

Permalink
Initial specification text for method invokers
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Nov 14, 2023
1 parent 3855bf0 commit 2e4bda8
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,11 @@ public interface BeanInfo {
Collection<InjectionPointInfo> injectionPoints();

/**
* Returns a new {@link InvokerBuilder} for given method. The builder eventually produces
* an opaque representation of the invoker for the given method.
* Returns a new {@link InvokerBuilder} for given method of this bean. The builder
* eventually produces an opaque representation of the generated invoker.
* <p>
* 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.
* <p>
* If an invoker may not be obtained for given {@code method} as described
* in {@link jakarta.enterprise.invoke.Invoker Invoker}, an exception is thrown.
* <p>
* If this method is called outside the {@code @Registration} phase, an exception is thrown.
* If an invoker may not be built for this bean or for given {@code method},
* an exception is thrown.
*
* @param method method of this bean, must not be {@code null}
* @return the invoker builder, never {@code null}
Expand Down
126 changes: 28 additions & 98 deletions api/src/main/java/jakarta/enterprise/invoke/Invoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,122 +11,52 @@
package jakarta.enterprise.invoke;

/**
* Allows indirectly invoking a method that belongs to a managed bean (the <em>target method</em>).
* 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.
* <p>
* 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.
* <p>
* 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.
*
* <h2>Obtaining an invoker</h2>
*
* 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.
* <p>
* 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.
* <p>
* 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.
*
* <h2>Example</h2>
*
* 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:
* <pre>
* &#64;Dependent
* class MyService {
* String hello(String name) {
* public class MyService {
* public String hello(String name) {
* return "Hello " + name + "!";
* }
* }
* </pre>
*
* 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:
*
* <pre>
* InvokerBuilder&lt;Invoker&lt;MyService, String&gt;&gt; builder = ...;
* Invoker&lt;MyService, String&gt; invoker = builder.build();
* </pre>
*
* In a build compatible extension, this results in an opaque token that later
* materializes as an invoker:
*
* <pre>
* InvokerBuilder&lt;InvokerInfo&gt; builder = ...;
* InvokerInfo invoker = builder.build();
* </pre>
*
* To call the {@code hello()} method through this invoker, call
* {@code invoker.invoke(myService, new Object[] {"world"})}.
* The return value is {@code "Hello world!"}.
* <p>
* An implementation of the direct invoker above is equivalent to the following class:
*
* Further, assume that {@code invoker} is an invoker for the {@code hello()} method
* of the {@code MyService} bean and {@code myService} is an instance of the bean.
* Then, to invoke the {@code hello()} method indirectly, a framework would call
* <pre>
* class TheInvoker implements Invoker&lt;MyService, String&gt; {
* String invoke(MyService instance, Object[] arguments) {
* return instance.hello((String) arguments[0]);
* }
* }
* invoker.invoke(myService, new Object[] {"world"})
* </pre>
* The return value would be {@code "Hello world!"}.
*
* @param <T> type of the target instance
* @param <R> return type of the method
* @param <T> type of the target bean
* @param <R> return type of the target method
* @since 4.1
* @see #invoke(Object, Object[])
*/
public interface Invoker<T, R> {
/**
* 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.
* <p>
* 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[]}.
* 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, this
* method rethrows it directly.
* <p>
* 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.
* <p>
* 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)
* The {@code instance} must be assignable to the target bean. Each of the {@code arguments}
* must be assignable to the corresponding parameter of the target method.
*
* @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}
*/
R invoke(T instance, Object[] arguments); // TODO throws Exception ?
}
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,7 @@ public interface InvokerBuilder<T> {

/**
* 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.
* but not required to reuse already built invokers when possible.
*
* @return the built invoker
*/
Expand Down
4 changes: 4 additions & 0 deletions spec/src/main/asciidoc/cdi-spec.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand All @@ -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[]
Expand Down
181 changes: 181 additions & 0 deletions spec/src/main/asciidoc/core/invokers.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
[[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 <<enablement,enabled>> <<managed_beans,managed bean>>.
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 of 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.

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 <<invoker_builder>> for more information.

[[using_invoker]]
=== Using an `Invoker`

The `Invoker` interface contains a single method:

[source,java]
----
public interface Invoker<T, R> {
R invoke(T instance, Object[] arguments);
}
----

Calling `invoke()` invokes the target method on given `instance` of the target bean, passing given `arguments`.

Invoker implementations must be thread-safe.
A single invoker instance may be used to perform multiple, possibly concurrent, invocations of the target method, possibly on different instances of the target bean, possibly with different arguments.

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 returns normally, its return value is returned, unless the target method is declared `void`, in which case `null` is returned.
If the target method throws an exception, it is rethrown.
The `invoke()` method does not declare any checked exception; implementations are supposed to use the "sneaky throw" idiom.
// TODO maybe change that?

If the target method is not `static` and `instance` is `null`, a `NullPointerException` is thrown.
If the target method is not `static` and the `instance` is not <<invoker_assignability,assignable>> to the target bean, a `ClassCastException` is thrown.

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).

If the target method declares no parameter, `arguments` are ignored.
If the target method declares any parameter and `arguments` is `null`, `NullPointerException` is thrown.
If the `arguments` array has fewer elements than the number of parameters of the target method, `ArrayIndexOutOfBoundsException` is thrown.
If the `arguments` array has more elements than the number of parameters of the target method, the excess elements are ignored.
If some of the `arguments` is not <<invoker_assignability,assignable>> to the corresponding parameter of the target method, `ClassCastException` is thrown.

If the declared type of a parameter of the target method is a primitive type, then:

* if the corresponding argument is `null`, the default value of the primitive type is passed;
* if the corresponding argument is not `null`, unboxing conversion is performed on the argument and the result is passed.

If the declared type of a parameter of the target method is not a primitive type, the corresponding argument is passed as is.

// 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, this results in an invoker which should be stored for later usage:

[source,java]
----
InvokerBuilder<Invoker<MyService, String>> builder = ...;
Invoker<MyService, String> invoker = builder.build();
----

In a build compatible extension, this results in an opaque token that materializes as an `Invoker` at application runtime:

[source,java]
----
InvokerBuilder<InvokerInfo> builder = ...;
InvokerInfo invoker = builder.build();
----

To call the `hello()` method through this invoker, assuming that `myService` is an instance of 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<MyService, String> {
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 `BeanInfo.createInvoker()`:

[source,java]
----
public interface BeanInfo {
...
InvokerBuilder<InvokerInfo> createInvoker(MethodInfo method);
}
----

The target bean of the created invoker is the bean represented by the `BeanInfo` object.
The target method of the created invoker is the method represented by the `MethodInfo` object passed to `createInvoker()`.

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_assignability]]
=== Assignability for invokers

For the purpose of `Invoker.invoke()`, assignability is defined as:

* a non-`null` instance is assignable to the target bean when the class of the instance is a subclass of the bean class of the target bean;
* a `null` argument is assignable to any parameter of the target method;
* a non-`null` argument is assignable to a parameter of the target method when the class of the argument is assignable to the erasure of the declared type of the parameter, as defined below.

If an argument class is denoted `A` and the erasure of the declared type of a parameter is denoted `P`, then:

* `A` is assignable to a primitive type `P` when `A` is the primitive wrapper class of `P`;
* `A` is assignable to a class or interface type `P` when:
** `A` is a class or interface type and `A` is a subclass of `P`, or
** `A` is an array type and `P` is `java.lang.Object`, `java.lang.Cloneable`, or `java.io.Serializable`;
* `A` is assignable to an array type `P` when `A` is an array type and:
** the component type of `P` is a primitive type and the component types of `A` and `P` are identical, or
** the component type of `P` is not a primitive type and the component type of `A` is assignable to the component type of `P` according to these rules.

NOTE: This is a reformulation of the JVM assignability rules as defined by the specification of the `checkcast` and `instanceof` instructions, with addition of primitive types.
It is expected that CDI implementations do not implement these rules and rely on JVM runtime checks.

When the declared type of a parameter of the target method is not a reifiable type, callers of `Invoker.invoke()` must also ensure that the corresponding argument is constructed appropriately.
Otherwise, runtime failures are likely to occur.

0 comments on commit 2e4bda8

Please sign in to comment.