Skip to content

Commit

Permalink
Initial proposal for method invokers: introduce core concepts
Browse files Browse the repository at this point in the history
_`Invoker`_ is an indirect way of invoking a bean method. It is a simple
functional interface:

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

Invokers are built using an _`InvokerBuilder`_. They must be thread-safe,
so frameworks can build an invoker for each relevant method once and then
reuse it to invoke that method whenever necessary.

An invoker may be built for a method that:

1. is not `private`;
2. is not a constructor;
3. is declared on a bean class of a managed bean (class-based bean),
   with the exception of interceptors and decorators, or inherited
   from its supertypes.

If an invocation of a method is intercepted, an equivalent invocation
through an invoker is also intercepted.

Portable extensions may obtain an `InvokerBuilder` in an observer
of the `ProcessManagedBean` type. The `InvokerBuilder` in this case
produces `Invoker` objects directly.

Build compatible extensions may obtain an `InvokerBuilder` during
`@Registration` from the `BeanInfo` object. The `InvokerBuilder` in
this case produces `InvokerInfo` objects. `InvokerInfo` is an opaque
token that can only be used to materialize an `Invoker` in synthetic
components.
  • Loading branch information
Ladicek authored and manovotn committed Oct 13, 2023
1 parent bf4dbf1 commit 15e84a0
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -162,6 +163,25 @@ 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.
* <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.
*
* @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<InvokerInfo> createInvoker(MethodInfo method);

// ---

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -40,13 +42,16 @@
* <p>
* Annotation-typed parameters are available as instances of the annotation type,
* even if an instance of {@code AnnotationInfo} was passed to the builder.
* <p>
* 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 <T> 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
Expand All @@ -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 <T> the parameter type
* @return the parameter value, or {@code defaultValue} if parameter with given {@code key} does not exist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,36 @@ public interface SyntheticBeanBuilder<T> {
*/
SyntheticBeanBuilder<T> 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.
* <p>
* 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}, <em>not</em> 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<T> 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.
* <p>
* When looked up from the parameter map in the creation/destruction function, the values will be
* instances of {@link jakarta.enterprise.invoke.Invoker Invoker}, <em>not</em> {@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<T> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,34 @@ public interface SyntheticObserverBuilder<T> {
*/
SyntheticObserverBuilder<T> 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.
* <p>
* 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}, <em>not</em> 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<T> 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.
* <p>
* When looked up from the parameter map in the event notification function, the values will be
* instances of {@link jakarta.enterprise.invoke.Invoker Invoker}, <em>not</em> {@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<T> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package jakarta.enterprise.inject.spi;

import jakarta.enterprise.invoke.Invoker;
import jakarta.enterprise.invoke.InvokerBuilder;

/**
* <p>
* The container fires an event of this type for each enabled managed bean, before registering the
Expand All @@ -38,4 +41,17 @@ public interface ProcessManagedBean<X> extends ProcessBean<X> {
* @throws IllegalStateException if called outside of the observer method invocation
*/
public AnnotatedType<X> getAnnotatedBeanClass();

/**
* Returns a new {@link InvokerBuilder} for given method. The builder eventually produces an invoker
* for the given method.
* <p>
* 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<Invoker<X, ?>> createInvoker(AnnotatedMethod<? super X> method);
}
128 changes: 128 additions & 0 deletions api/src/main/java/jakarta/enterprise/invoke/Invoker.java
Original file line number Diff line number Diff line change
@@ -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 <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}.
* <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.
* <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:
*
* <pre>
* &#64;Dependent
* class MyService {
* 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:
*
* <pre>
* class TheInvoker implements Invoker&lt;MyService, String&gt; {
* String invoke(MyService instance, Object[] arguments) {
* return instance.hello((String) arguments[0]);
* }
* }
* </pre>
*
* @param <T> type of the target instance
* @param <R> return type of the 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[]}.
* <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!
*
* @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 ?
}
28 changes: 28 additions & 0 deletions api/src/main/java/jakarta/enterprise/invoke/InvokerBuilder.java
Original file line number Diff line number Diff line change
@@ -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 <T> 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<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.
*
* @return the built invoker
*/
T build();
}
1 change: 1 addition & 0 deletions api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 15e84a0

Please sign in to comment.