Skip to content

Commit

Permalink
Initial proposal for invokable methods: introduce core concepts
Browse files Browse the repository at this point in the history
This commit introduces core concepts for CDI invokable methods.

An _invokable method_ is 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),
   or inherited from its supertypes;
4. is annotated with an invokable marker annotation, or is declared
   on a class that is annotated with an invokable marker annotation.

An _invokable marker_ annotation is `@Invokable` or any annotation
that is meta-annotated `@Invokable`, directly or transitively.
That is, application developers may declare invokable methods
directly, but that is not the expected common case. It is expected
that frameworks will provide their own annotations that will,
in addition to their framework-specific nature, also serve
as invokable markers. For example, if JAX-RS were to be based
on CDI, the `@HttpMethod` annotation would be `@Invokable`.

_`Invoker`_ is an indirect way of invoking an invokable 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 stateless
and thread-safe, so frameworks can build an invoker for each relevant
invokable method once and then reuse it to invoke that method whenever
necessary.

If an invokable method is intercepted, invocation through an invoker
is also intercepted.

Portable extensions may inspect invokable methods and obtain an
`InvokerBuilder` in an observer of the `ProcessManagedBean` type.
The `InvokerBuilder` in this case produces `Invoker` objects directly.
iPortable extensions may declare new invokable marker annotations
through `BeforeBeanDiscovery`.

Build compatible extensions may inspect invokable methods and 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. Build compatible extensions may
declare new invokable marker annotations through `MetaAnnotations`.
  • Loading branch information
Ladicek committed Jul 18, 2023
1 parent 37afd0e commit a796dfc
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 6 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,32 @@ public interface BeanInfo {
*/
Collection<InjectionPointInfo> injectionPoints();

/**
* Returns all {@linkplain jakarta.enterprise.invoke.Invokable invokable} methods that belong to this bean.
* <p>
* Note that only managed beans may have invokable methods; calling this method on a producer bean
* or a synthetic bean results in an empty collection.
*
* @return immutable collection of invokable methods, never {@code null}
* @since 4.1
*/
Collection<MethodInfo> invokableMethods();

/**
* Returns a new builder of an invoker for given invokable method. The builder eventually
* produces an opaque representation of the invoker.
* <p>
* The {@code method} must be an invokable method that belongs to this bean, otherwise
* an exception is thrown.
*
* @param method invokable method belonging to 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, or at least
// specify that this method may only be called during `@Registration`
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#registerInvoker(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 @@ -59,6 +59,18 @@ public interface MetaAnnotations {
*/
ClassConfig addStereotype(Class<? extends Annotation> annotation);

/**
* Registers {@code annotation} as an invokable marker annotation. Only makes sense if the annotation
* is not meta-annotated {@code @Invokable}.
* <p>
* Returns a {@linkplain ClassConfig class configurator} object that allows transforming meta-annotations
* on the {@code annotation}.
*
* @param annotation annotation type
* @since 4.1
*/
void addInvokable(Class<? extends Annotation> annotation);

/**
* Registers custom context for given {@code scopeAnnotation} and given {@code contextClass}.
* CDI container will create an instance of the context class once to obtain the context object.
Expand Down
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 @@ -41,7 +41,7 @@
public interface BeforeBeanDiscovery {
/**
* <p>
* Declares an annotation type as a {@linkplain jakarta.inject.Qualifier} qualifier type.
* Declares an annotation type as a {@linkplain jakarta.inject.Qualifier qualifier type}.
* </p>
*
* <p>
Expand All @@ -55,7 +55,7 @@ public interface BeforeBeanDiscovery {

/**
* <p>
* Declares an annotation type as a {@linkplain jakarta.inject.Qualifier} qualifier type.
* Declares an annotation type as a {@linkplain jakarta.inject.Qualifier qualifier type}.
* </p>
*
* <p>
Expand Down Expand Up @@ -138,6 +138,22 @@ public interface BeforeBeanDiscovery {
*/
public void addInterceptorBinding(Class<? extends Annotation> bindingType, Annotation... bindingTypeDef);

/**
* <p>
* Declares an annotation type as an {@linkplain jakarta.enterprise.invoke.Invokable invokable} marker.
* </p>
*
* <p>
* This is only required if you wish to make an annotation an invokable marker without adding
* {@link jakarta.enterprise.invoke.Invokable} to it.
* </p>
*
* @param invokable The annotation to treat as an invokable marker
* @throws IllegalStateException if called outside of the observer method invocation
* @since 4.1
*/
public void addInvokable(Class<? extends Annotation> invokable);

/**
* <p>
* Adds a given {@link AnnotatedType} to the set of types which will be scanned during bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
*/
package jakarta.enterprise.inject.spi;

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

import java.util.Collection;

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

/**
* Returns all {@linkplain jakarta.enterprise.invoke.Invokable invokable} methods that belong to this bean.
*
* @return immutable collection of invokable methods, never {@code null}
* @since 4.1
*/
// TODO if we introduce `ProcessInvokableMethod`, this method would be removed
public Collection<AnnotatedMethod<? super X>> getInvokableMethods();

/**
* Returns a new builder of an invoker for given invokable method. The builder eventually
* produces the invoker.
* <p>
* The {@code method} must be an invokable method that belongs to this bean, otherwise
* an exception is thrown.
*
* @param method invokable method belonging to this bean, must not be {@code null}
* @return the invoker builder, never {@code null}
* @since 4.1
*/
// TODO if we introduce `ProcessInvokableMethod`, this method would be moved there
public InvokerBuilder<Invoker<X, ?>> createInvoker(AnnotatedMethod<? super X> method);
}
51 changes: 51 additions & 0 deletions api/src/main/java/jakarta/enterprise/invoke/Invokable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a method as <em>invokable</em>. For each invokable method belonging to
* a managed bean class, the CDI container allows {@linkplain InvokerBuilder building}
* an {@link Invoker} through which the method can be invoked.
* <p>
* Attempting to mark a private method or a constructor of a managed bean class
* as invokable is a definition error.
* <p>
* Methods that are marked as invokable but do not belong to managed beans are ignored.
* <p>
* When this annotation is present on a class or interface, it marks all non-private
* methods (except constructors) declared in this class as invokable. Note that this
* does not apply to methods declared in superclasses or superinterfaces.
* TODO interfaces are not managed beans and annotations are never inherited from
* interfaces, so maybe just remove the mentions of interfaces altogether?
* <p>
* Note that multiple managed beans may inherit an invokable method from a common
* supertype. In that case, each bean conceptually has its own invokable method
* and for example an invoker obtained for one bean cannot be used to invoke
* the method on the other bean.
* <p>
* This annotation may be used as a meta-annotation. Classes and methods annotated with
* an annotation that is directly or transitively meta-annotated {@code @Invokable}
* are treated as if they were directly annotated {@code @Invokable}.
*
* @since 4.1
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// TODO @Inherited ?
public @interface Invokable {
}
Loading

0 comments on commit a796dfc

Please sign in to comment.