Skip to content

Commit

Permalink
CDI-580 Allow interceptors to be applied to the return value of a pro…
Browse files Browse the repository at this point in the history
…ducer (#315)
  • Loading branch information
antoinesd committed Nov 18, 2016
1 parent 5fdf384 commit b155833
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 9 deletions.
22 changes: 17 additions & 5 deletions api/src/main/java/javax/enterprise/inject/spi/BeanManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@

package javax.enterprise.inject.spi;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.enterprise.context.ContextNotActiveException;
Expand All @@ -33,6 +28,10 @@
import javax.enterprise.inject.InjectionException;
import javax.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.util.Nonbinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

/**
* <p>
Expand Down Expand Up @@ -89,6 +88,7 @@
*/
public interface BeanManager {


/**
* <p>
* Obtains a contextual reference for a certain {@linkplain Bean bean} and a certain bean type of the bean.
Expand Down Expand Up @@ -606,4 +606,16 @@ public <T> Bean<T> createBean(BeanAttributes<T> attributes, Class<T> beanClass,
*/
public <T extends Extension> T getExtension(Class<T> extensionClass);

/**
*
* Create an {@link InterceptionFactory} for the given {@link CreationalContext} and type.
*
* @param ctx {@link CreationalContext} for the {@link InterceptionFactory} to create
* @param clazz class of the instance this factory will work on
* @param <T> type of the instance this factory will work on
* @return a new {@link InterceptionFactory} to add services on on instances of T
* @since 2.0
*/
<T> InterceptionFactory<T> createInterceptionFactory(CreationalContext<T> ctx, Class<T> clazz);

}
126 changes: 126 additions & 0 deletions api/src/main/java/javax/enterprise/inject/spi/InterceptionFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2016, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package javax.enterprise.inject.spi;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;

/**
* <p>An {@link InterceptionFactory} adds the services available for instances created by the container to any instance.
* It makes each method invocation in the instance a business method invocation as defined in section 7.2 of the specification document
* It will bind business methods only to <tt>@AroundInvoke</tt> interceptor methods.
* </p>
*
* <p>An implementation of {@link InterceptionFactory} may be obtain by
* calling {@link BeanManager#createInterceptionFactory(CreationalContext, Class)}
* to be used in the create method of a custom bean for example.</p>
*
*
* <pre>
* public class MyCustomBean implements Bean&lt;MyClass&gt; {
*
* BeanManager bm;
*
* public MyBean(BeanManager bm) {
* this.bm = bm;
* }
*
* ...
*
* public MyClass create(CreationalContext&lt;MyClass&gt; creationalContext) {
*
* InterceptionFactory<MyClass> factory = bm.createInterceptionFactory(creationalContext, MyClass.class);
*
* factory.configure()
* .filterMethods(m -> m.getJavaMember().getName().equals("shouldBeTransactional"))
* .findFirst()
* .ifPresent(m -> m.add(new AnnotationLiteral&lt;Transactional&gt;() { }));
*
* return factory.createInterceptedInstance(new MyClass());
* }
*
* ...
* }
* </pre>
*
* The container also provides a built-in bean for {@link InterceptionFactory} that can be injected in a producer method parameters to apply
* interceptors on the produced instance.
*
* <pre>
* &#64;Produces
* &#64;RequestScoped
* public MyClass produceMyClass(InterceptionFactory<MyClass> factory) {
* factory.configure().add(new AnnotationLiteral<Transactional>() {});
* return factory.createInterceptedInstance(new MyClass());
* }
*
* </pre>
*
*
*
* InterceptionFactory is not reusable
*
* @author Antoine Sabot-Durand
* @since 2.0
* @param <T> type for which the proxy is created
*/
public interface InterceptionFactory<T> {

/**
*
* <p>Instructs the container to ignore all non-static, final methods with public, protected or default visibility declared on
* any bean type of the specific bean during validation of injection points that require proxyable bean type.</p>
*
* <p>These method should never be invoked upon bean instances. Otherwise, unpredictable behavior results.</p>
*
*
*
* @return self
*/
InterceptionFactory<T> ignoreFinalMethods();

/**
*
* Returns an {@link AnnotatedTypeConfigurator} to allow addition of interceptor binding on the instance's methods.
* The matching annotatedType will be used to apply the interceptors when calling createInterceptedInstance method.
* Annotations that are not interceptor binding will be ignored.
*
* Each call returns the same AnnotatedTypeConfigurator.
*
* @return an {@link AnnotatedTypeConfigurator} to configure interceptors bindings
*/
AnnotatedTypeConfigurator<T> configure();

/**
* Returns an enhanced version of the instance for which each method invocations will be a business method invocation.
* Invocation will pass through Method interceptors defined in T class or in the {@link AnnotatedTypeConfigurator}
* defined with the configure method.
*
* This method should ony be called once, subsequent calls will throw an {@link IllegalStateException}
*
* If T is not proxyable as defined in section 3.11 of the spec an {@link javax.enterprise.inject.UnproxyableResolutionException} exception is thrown.
* Calling ignoreFinalMethods before this method can loosen this restriction.
*
* If the instance was created by the container, this method does nothing and returns the provided instance.
*
* @param instance on which container should add its services
* @return an enhanced instance whose method invocations will be business method invocations
*/
T createInterceptedInstance(T instance);

}
4 changes: 3 additions & 1 deletion spec/src/main/asciidoc/core/decorators.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ These bean types are called _decorated types_.

Decorators are superficially similar to interceptors, but because they directly implement operations with business semantics, they are able to implement business logic and, conversely, unable to implement the cross-cutting concerns for which interceptors are optimized.

Decorators may be associated with any managed bean that is not itself an interceptor or decorator, or with any built-in bean other than the built-in bean with type `BeanManager` and qualifier `@Default`. Decorators are not applied to the return value of a producer method or the current value of a producer field.
Decorators may be associated with any managed bean that is not itself an interceptor or decorator, or with any built-in bean other than the built-in bean with type `BeanManager` and qualifier `@Default`.
Decorators are not automatically applied to the return value of a producer method or the current value of a producer field.

A decorator instance is a dependent object of the object it decorates.

[[decorator_bean]]
Expand Down
2 changes: 2 additions & 0 deletions spec/src/main/asciidoc/core/interceptors.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ An interceptor binding declared on a bean class replaces an interceptor binding

The set of interceptor bindings for a producer method is not used to associate interceptors with the return value of the producer method.

Since CDI 2.0 it is possible to apply interceptors programmatically to the return value of a producer method, with the `InterceptionFactory` interface as defined in <<interception_factory>>.

If a managed bean has a class-level or method-level interceptor binding, the managed bean must be a proxyable bean type, as defined in <<unproxyable>>.

[[enabled_interceptors]]
Expand Down
5 changes: 3 additions & 2 deletions spec/src/main/asciidoc/core/lifecycle.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ In this case, any object returned by the producer method will not have any depen

When the application invokes:

* a method of a bean via a contextual reference to the bean, as defined in <<contextual_reference>>, or
* a method of a bean via a non-contextual reference to the bean, if the instance was created by the container (e.g. using `InjectionTarget.produce()` or `UnmanagedInstance.produce()`),
* a method of a bean via a contextual reference to the bean, as defined in <<contextual_reference>>,
* a method of a bean via a non-contextual reference to the bean, if the instance was created by the container (e.g. using `InjectionTarget.produce()` or `UnmanagedInstance.produce()`), or
* a method of a bean via a non-contextual reference to the bean, if the instance was enhanced with the `InterceptionFactory` interface as defined in <<interception_factory>>,

the invocation is treated as a _business method invocation_.

Expand Down
49 changes: 48 additions & 1 deletion spec/src/main/asciidoc/core/spi.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ public interface Producer<T> {

For a `Producer` that represents a class:

* `produce()` calls the constructor annotated `@Inject` if it exists, or the constructor with no parameters otherwise, as defined in <<instantiation>>, and returns the resulting instance. If the class has interceptors, `produce()` is responsible for building the interceptors and decorators of the instance. The instance returned by `produce()` may be a proxy.
* `produce()` calls the constructor annotated `@Inject` if it exists, or the constructor with no parameters otherwise, as defined in <<instantiation>>, and returns the resulting instance. If the class has interceptors, `produce()` is responsible for building the interceptors and decorators of the instance.
The instance returned by `produce()` may be a proxy.
* `dispose()` does nothing.
* `getInjectionPoints()` returns the set of `InjectionPoint` objects representing all injected fields, bean constructor parameters and initializer method parameters.

Expand Down Expand Up @@ -661,6 +662,18 @@ The method `BeanManager.getExtension()` returns the container's instance of an `
public <T extends Extension> T getExtension(Class<T> extensionClass);
----

[[bm_obtain_interception_factory]]

==== Obtain an `InterceptionFactory`

The method `BeanManager.getInterceptionFactory()` returns an `InterceptionFactory` for the provided type as defined in <<interception_factory>>.

[source, java]
----
<T> InterceptionFactory<T> createInterceptionFactory(CreationalContext<T> ctx, Class<T> clazz);
----


[[alternative_metadata_sources]]

=== Alternative metadata sources
Expand Down Expand Up @@ -1591,3 +1604,37 @@ CDI 2.0 introduced the following Configurators interface:
* <<observer_method_configurator>> for `ObserverMethod` configuration

The container must provide implementation for all these configurators and make them available in matching container lifecycle events as defined in <<init_events>>.

[[interception_factory]]

=== Apply interceptor programmatically

CDI 2.0 introduces the `javax.enterprise.inject.spi.InterceptionFactory<T>` interface to add services to an instance not created by the container by making each of its method invocation, a business method invocation as defined in <<biz_method>>.
It will bind business methods only to `@AroundInvoke` interceptor methods.


[source, java]
----
public interface InterceptionFactory<T> {
InterceptionProxyFactory<T> ignoreFinalMethods();
AnnotatedTypeConfigurator<T> configure();
T createInterceptedInstance(T instance);
}
----

* `ignoreFinalMethods()` `ignoreFinalMethods()` Instructs the container to ignore all non-static, final methods with public, protected or default visibility declared on any bean type of the specific bean during validation of injection points that require proxyable bean type.
These method should never be invoked upon bean instances. Otherwise, unpredictable behavior results.
It will bypass standard rules defined in <<unproxyable>>.
* `configure()` returns an `AnnotatedTypeConfigurator` (as defined in <<annotated_type_configurator>>) initialized with the instance type to easily apply specific interceptor binding to apply when enhancing the instance.
The method always return the same `AnnotatedTypeConfigurator`
* `createInterceptedInstance()` returns an enhanced version of the instance for which each method invocations will be a business method invocation.
The method can be only called once, subsequent calls will throw an exception.
If the type of the instance is not proxyable as defined in <<unproxyable>> an `UnproxyableResolutionException` exception is thrown.
This rule can be loosen by calling `ignoreFinalMethods()` before this method.
If called on an instance created by the container, this method does nothing and returns the provided instance.

An `InterceptionFactory` can be obtain be calling `BeanManager.createInterceptionFactory` as defined in <<bm_obtain_interception_factory>>

The container must provide a bean with scope `@Dependent`, bean type `InterceptionFactory` and qualifier `@Default` allowing producer methods to obtain this factory to produce their instance.

If an injection point of type `InterceptionFactory` and qualifier @Default which is not a parameter of a producer method exists, the container automatically detects the problem and treats it as a definition error.

0 comments on commit b155833

Please sign in to comment.