Interface InvokerBuilder<T>
- Type Parameters:
T
- type of outcome of this builder; always represents anInvoker
, but does not necessarily have to be anInvoker
instance directly
Invoker
s that allows configuring input lookups, input and output
transformations, and invoker wrapping. The method for which the invoker is built is
called the target method. If a lookup is configured, the corresponding input
of the invoker is ignored and an instance is looked up from the CDI container before
the target method is invoked. If a transformation is configured, the corresponding input
or output of the invoker is modified in certain way before or after the target method
is invoked. If a wrapper is configured, the invoker is passed to custom code for getting
invoked. As a result, the built Invoker
instance may have more complex behavior
than just directly calling the target method.
Transformations and wrapping are expressed by ordinary methods that must have a pre-defined signature, as described below. Such methods are called transformers and wrappers.
Invokers may only be built during deployment. It is not possible to build new invokers at application runtime.
Example
Before describing in detail how lookups, transformers and wrappers work, let's take a look at an example. Say we have the following bean with a method:class MyService { String hello(String name) { return "Hello " + name + "!"; } }And we want to build an invoker that looks up
MyService
from the CDI container,
always passes the argument to hello()
as all upper-case, and repeats the return
value twice. To transform the argument, we can use the zero-parameter method
String.toUpperCase()
, and to transform the return value, we write a transformer
as a simple static
method:
class Transformations { static String repeatTwice(String str) { return str + " " + str; } }Then, assuming we have obtained the
InvokerBuilder
for MyService.hello()
,
we can set up the lookup and transformations and build an invoker like so:
builder.setInstanceLookup() .setArgumentTransformer(0, String.class, "toUpperCase") .setReturnValueTransformer(Transformations.class, "repeatTwice") .build();The resulting invoker will be equivalent to the following class:
class TheInvoker implements Invoker<MyService, String> { String invoke(MyService ignored, Object[] arguments) { MyService instance = CDI.current().select(MyService.class).get(); String argument = (String) arguments[0]; String transformedArgument = argument.toUpperCase(); String result = instance.hello(transformedArgument); String transformedResult = Transformations.repeatTwice(result); return transformedResult; } }The caller of this invoker may pass
null
as the target instance, because
the invoker will lookup the target instance on its own. Therefore, calling
invoker.invoke(null, new Object[] {"world"})
will return
"Hello WORLD! Hello WORLD!"
.
General requirements
To refer to a transformer or a wrapper, all methods in this builder accept: 1. theClass
that that declares the method, and 2. the String
name
of the method.
Transformers may be static
, in which case they must be declared directly
on the given class, or they may be instance methods, in which case they may be declared
on the given class or inherited from any of its supertypes.
It is possible to register only one transformer of each kind, or for each argument position in case of argument transformers. Attempting to register a second transformer of the same kind, or for the same argument position, leads to an exception.
Wrappers must be static
and must be declared directly on the given class.
It is possible to register only one wrapper. Attempting to register a second wrapper
leads to an exception.
It is a deployment problem if no method with given name and valid signature is found,
or if multiple methods with given name and different valid signatures are found. It is
a deployment problem if a registered transformer or wrapper is not public
.
Transformers and wrappers may declare the throws
clause. The declared exception
types are ignored when searching for the method.
For the purpose of the specification of transformers and wrappers below, the term
any-type is recursively defined as: the java.lang.Object
class type,
or a type variable that has no bound, or a type variable whose first bound is
any-type.
Input lookups
For the target instance and for each argument, it is possible to specify that the value passed toInvoker.invoke()
should be ignored and a value should be looked up
from the CDI container instead.
For the target instance, a CDI lookup is performed with the required type equal to the bean
class of the bean to which the target method belongs, and required qualifiers equal to the set
of all qualifier annotations present on the bean class of the bean to which the target method
belongs. When the target method is static
, the target instance lookup is skipped.
For an argument, a CDI lookup is performed with the required type equal to the type of the corresponding parameter of the target method, and required qualifiers equal to the set of all qualifier annotations present on the corresponding parameter of the target method.
Implementations are required to resolve all lookups during deployment. It is a deployment problem if the lookup ends up unresolved or ambiguous.
If the looked up bean is @Dependent
, it is guaranteed that the instance will be
destroyed after the target method is invoked but before the the invoker returns. The order
in which the looked up @Dependent
beans are destroyed is not specified.
The order in which input lookups are performed in not specified and must not be relied upon.
Input transformations
The target method has 2 kinds of inputs: the target instance (unless the target method isstatic
, in which case the target instance is ignored and should be null
by convention) and arguments. These inputs correspond to the parameters of
Invoker.invoke()
.
Each input can be transformed by a transformer that has one of the following signatures,
where X
and Y
are types:
static X transform(Y value)
static X transform(Y value, Consumer<Runnable> cleanup)
X transform()
– in this case,Y
is the type of the class that declares the transformer
X
is any-type, it is not type checked during deployment.
Otherwise, it is a deployment problem if X
is not assignable to the corresponding type
in the declaration of the target method (that is the bean class in case of target instance
transformers, or the corresponding parameter type in case of argument transformers). Y
is not type checked during deployment, so that input transformers may consume arbitrary types.
TODO this paragraph refers to "assignability", which needs to be defined somewhere!
When a transformer is registered for given input, it is called before the target method is invoked, and the outcome of the transformer is used in the invocation instead of the original value passed to the invoker by its caller.
If the transformer declares the Consumer<Runnable>
parameter, and the execution
of the transformer calls Consumer.accept()
with some Runnable
, it is
guaranteed that the Runnable
will be called after the target method is invoked but
before the invoker returns. These Runnable
s are called cleanup tasks.
The order of cleanup task execution is not specified. Passing a null
cleanup task
to the Consumer
is permitted, but has no effect.
If an input transformation is configured for an input for which a lookup is also configured,
the lookup is performed first and the transformation is applied to the looked up value.
If the looked up bean for some input is @Dependent
, it is guaranteed that all
cleanup tasks registered by a transformer for that input are called before that looked up
@Dependent
bean is destroyed.
The order in which input transformations are performed in not specified and must not be relied upon.
Output transformations
The target method has 2 kinds of outputs: the return value and the thrown exception. These outputs correspond to the return value ofInvoker.invoke()
or its thrown exception, respectively.
Each output can be transformed by a transformer that has one of the following signatures,
where X
and Y
are types:
static X transform(Y value)
X transform()
– in this case,Y
is the type of the class that declares the transformer
Y
is any-type, it is not type checked during deployment.
Otherwise, it is a deployment problem if Y
is not assignable from the return type of
the target method in case of return value transformers, or from java.lang.Throwable
in case of exception transformers. X
is not type checked during deployment, so that
output transformers may produce arbitrary types.
TODO this paragraph refers to "assignability", which needs to be defined somewhere!
When a transformer is registered for given output, it is called after the target method is invoked, and the outcome of the transformer is passed back to the caller of the invoker instead of the original output produced by the target method.
If the target method returns normally, any registered exception transformer is ignored; only the return value transformer is called. The return value transformer may throw, in which case the invoker will rethrow the exception. If the invoker is supposed to return normally, the return value transformer must return normally.
Similarly, if the target method throws, any registered return value transformer is ignored; only the exception transformer is called. The exception transformer may return normally, in which case the invoker will return the return value of the exception transformer. If the invoker is supposed to throw an exception, the exception transformer must throw. TODO this requires that implementations catch java.lang.Throwable, which is perhaps a bit too much? maybe stick with java.lang.Exception?
Invoker wrapping
An invoker, possibly utilizing input lookups and input/output transformations, may be wrapped by a custom piece of code for maximum flexibility. A wrapper must have the following signature, whereX
, Y
and Z
are types:
static Z wrap(X instance, Object[] arguments, Invoker<X, Y> invoker)
X
is
any-type, it is not type checked during deployment. Otherwise, it is a deployment
problem if X
is not assignable from the class type of the bean class to which
the target method belongs. Y
and Z
are not type checked during deployment.
When a wrapper is registered, 2 invokers for the same method are created. The inner invoker applies all lookups and transformations, as described in previous sections, and invokes the target method. The outer invoker calls the wrapper with the passed instance and arguments and an instance of the inner invoker. The outer invoker is returned by this invoker builder.
In other words, the outer invoker is equivalent to the following class:
class InvokerWrapper implements Invoker<X, Z> { Z invoke(X instance, Object[] arguments) { // obtain the invoker as if no wrapper existed Invoker<X, Y> invoker = obtainInvoker(); return SomeClass.wrap(instance, arguments, invoker); } }If the wrapper returns normally, the outer invoker returns its return value, unless the wrapper is declared
void
, in which case the outer invoker returns null
. If the wrapper
throws an exception, the outer invoker rethrows it directly.
The wrapper is supposed to call the invoker it is passed, but does not necessarily have to. The wrapper may call the invoker multiple times. The wrapper must not use the invoker in any other way; specifically, it is forbidden to store the invoker instance anywhere or pass it to other methods that do not follow these rules. Doing so leads to non-portable behavior.
Type checking
An invoker created by this builder has relaxed type checking rules, when compared to the description inInvoker.invoke()
, depending
on configured lookups, transformers and wrapper. Some types are checked during
deployment, as described in previous sections. Other types are checked during invocation,
at the very least due to the type checks performed implicitly by the JVM. The lookups,
transformers and the wrapper must arrange the inputs and outputs so that when the method
is eventually invoked, the rules described in
Invoker.invoke()
all hold.
TODO specify what happens when a transformer/wrapper declares a parameter of a primitive type but the actual value passed to the invoker is `null` (the transformer should get a zero value?) TODO specify what happens when a transformer/wrapper declares a parameter of some type but the actual value passed to the invoker is not assignable to it (CCE?)
- Since:
- 4.1
-
Method Summary
Modifier and TypeMethodDescriptionbuild()
Returns the builtInvoker
or some represention of it.setArgumentLookup
(int position) Enables lookup of the argument on givenposition
.setArgumentTransformer
(int position, Class<?> clazz, String methodName) Configures an input transformer for the argument on givenposition
.setExceptionTransformer
(Class<?> clazz, String methodName) Configures an output transformer for the thrown exception.Enables lookup of the target instance.setInstanceTransformer
(Class<?> clazz, String methodName) Configures an input transformer for the target instance.setInvocationWrapper
(Class<?> clazz, String methodName) Configures an invoker wrapper.setReturnValueTransformer
(Class<?> clazz, String methodName) Configures an output transformer for the return value.
-
Method Details
-
setInstanceLookup
InvokerBuilder<T> setInstanceLookup()Enables lookup of the target instance.- Returns:
- this builder
-
setArgumentLookup
Enables lookup of the argument on givenposition
.- Parameters:
position
- zero-based argument position for which lookup is enabled- Returns:
- this builder
- Throws:
IllegalArgumentException
- ifposition
is greather than or equal to the number of parameters declared by the target method
-
setInstanceTransformer
Configures an input transformer for the target instance.- Parameters:
clazz
- class that declares the transformermethodName
- transformer method name- Returns:
- this builder
- Throws:
IllegalStateException
- if this method is called more than once
-
setArgumentTransformer
Configures an input transformer for the argument on givenposition
.- Parameters:
position
- zero-based argument position for which the input transformer is configuredclazz
- class that declares the transformermethodName
- transformer method name- Returns:
- this builder
- Throws:
IllegalArgumentException
- ifposition
is greather than or equal to the number of parameters declared by the target methodIllegalStateException
- if this method is called more than once with the sameposition
-
setReturnValueTransformer
Configures an output transformer for the return value.- Parameters:
clazz
- class that declares the transformermethodName
- transformer method name- Returns:
- this builder
- Throws:
IllegalStateException
- if this method is called more than once
-
setExceptionTransformer
Configures an output transformer for the thrown exception.- Parameters:
clazz
- class that declares the transformermethodName
- transformer method name- Returns:
- this builder
- Throws:
IllegalStateException
- if this method is called more than once
-
setInvocationWrapper
Configures an invoker wrapper.- Parameters:
clazz
- class that declares the invoker wrappermethodName
- invoker wrapper method name- Returns:
- this builder
- Throws:
IllegalStateException
- if this method is called more than once
-
build
T build()Returns the builtInvoker
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.- Returns:
- the built invoker
-