Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method invokers #639

Merged
merged 2 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
132 changes: 132 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,132 @@
/*
* 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!
*
* 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)
*
* @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 ?
}