diff --git a/src/main/asciidoc/entity-callbacks.adoc b/src/main/asciidoc/entity-callbacks.adoc index bd707ad7c..b2c41fb4b 100644 --- a/src/main/asciidoc/entity-callbacks.adoc +++ b/src/main/asciidoc/entity-callbacks.adoc @@ -3,23 +3,25 @@ The Spring Data infrastructure provides hooks for modifying an entity before and/or after certain methods are invoked. Those so called ``EntityCallback``s provide a convenient way to check and potentially modify an entity in a callback fashioned style. + -An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be familiar. -Some Spring Data implementations publish store specific events, like a `BeforeSaveEvent` that allow to modify the given entity, which in some cases, eg. immutable types, can cause somme trouble. -Plus, the event publishing relies on the `ApplicationEventMulticaster` which can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome. +An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be already familiar. +Some Spring Data modules publish store specific events, like a `BeforeSaveEvent` that allow modifying the given entity which in some cases, eg. immutable types, can cause trouble. +Another aspects is that, event publishing relies on `ApplicationEventMulticaster` that can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome as event processing can be forked onto a `Thread`. -``EntityCallback``s offer integration points with both sync and reactive APIs guaranteeing an in order invocation at fixed checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type. +Entity callbacks provide integration points with both, synchronous and reactive, APIs guaranteeing an in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type. + +Entity callbacks are typically separated by API type. This separation means that a synchronous API only considers synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks. [NOTE] ==== -The entity callback API has been introduced with Spring Data Commons 2.2 and is the recommended way of dealing with entity modifications. + -Existing store specific `ApplicationEvents` are still be published *before* the potentially registered ``EntityCallback``s are called. +The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications. +Existing store specific `ApplicationEvents` are still published *before* the invoking potentially registered ``EntityCallback``s. ==== [entity-callbacks.implement] == Implementing Entity Callbacks -The `EntityCallback` is, via its generic type argument, directly associated with the domain type it is meant for. -Each store typically uses a set of predefined entity callbacks covering the lifecycle of an entity. +An `EntityCallback` is directly associated with its domain type through its generic type argument. +Each Spring Data module typically ships a set of predefined `EntityCallback` interfaces covering the entity lifecycle. .Anatomy of an `EntityCallback` ==== @@ -38,7 +40,7 @@ public interface BeforeSaveCallback extends EntityCallback { } ---- <1> `BeforeSaveCallback` specific method to be called before an entity is saved. Returns a potentially modifed instance. -<2> The entity right bevore presisted. +<2> The entity right before persisting. <3> A number of store specific arguments like the _collection_ the entity is persisted to. ==== @@ -59,20 +61,22 @@ public interface ReactiveBeforeSaveCallback extends EntityCallback { } ---- <1> `BeforeSaveCallback` specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance. -<2> The entity right bevore presisted. +<2> The entity right before persisting. <3> A number of store specific arguments like the _collection_ the entity is persisted to. ==== -Implement the interface suiting your application needs like shown in the example below. +NOTE: Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of `EntityCallback.callback()`. + +Implement the interface suiting your application needs like shown in the example below: .Example `BeforeSaveCallback` ==== [source,java] ---- -public class DefaultingEntityCallback implements BeforeSaveCallback, Ordered <2> { +class DefaultingEntityCallback implements BeforeSaveCallback, Ordered <2> { @Override - public Object onBeforeSave(Person entity, String collection) { <1> + public Object onBeforeSave(Person entity, String collection) { <1> if(collection == "user) { return // ... @@ -83,74 +87,77 @@ public class DefaultingEntityCallback implements BeforeSaveCallback, Ord @Override public int getOrder() { - return 100; <2> + return 100; <2> } } ---- -<1> Implement logic according to application requirements. +<1> Callback implementation according to your requirements. <2> Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence. ==== [entity-callbacks.register] == Registering Entity Callbacks -``EntityCallback``s get picked up by the store specific implementations in case they are provided with an `ApplicationContext`. -Most template APIs already implement `ApplicationContextAware` an therefore have a context at hand if registered as a Bean. +``EntityCallback`` beans are picked up by the store specific implementations in case they are registered in the `ApplicationContext`. +Most template APIs already implement `ApplicationContextAware` and therefore have access to the `ApplicationContext` -The following example provides a collection of valid entity callback registrations. +The following example explains a collection of valid entity callback registrations: .Example `EntityCallback` Bean registration ==== [source,java] ---- +@Order(1) <1> +@Component +class First implements BeforeSaveCallback { + + @Override + public Person onBeforeSave(Person person) { + return // ... + } +} + +@Component +class DefaultingEntityCallback implements BeforeSaveCallback, + Ordered <2> { + + @Override + public Object onBeforeSave(Person entity, String collection) { + // ... + } + + @Override + public int getOrder() { + return 100; <2> + } +} + @Configuration public class EntityCallbackConfiguration { - @Bean - BeforeSaveCallback annotationOrderedCallback() { <1> - return new First(); - } - - @Bean - BeforeSaveCallback interfaceOrderedCallback() { <2> - return new DefaultingEntityCallback(); - } - @Bean BeforeSaveCallback unorderedLambdaReceiverCallback() { <3> return (BeforeSaveCallback) it -> // ... } +} - @Bean - UserCallbacks multipleCallbacksInOneImplementationClass() { <4> - return new UserCallbacks(); - } +@Component +class UserCallbacks implements BeforeConvertCallback, + BeforeSaveCallback { <4> - @Order(1) <1> - static class First implements BeforeSaveCallback { + @Override + public Person onBeforeConvert(User user) { + return // ... + } - @Override - public Person onBeforeSave(Person person) { - return // ... - } - } - - static class UserCallbacks implements BeforeConvertCallback, BeforeSaveCallback { <4> - - @Override - public Person onBeforeConvert(User user) { - return // ... - } - - @Override - public Person onBeforeSave(User user) { - return // ... - } - } + @Override + public Person onBeforeSave(User user) { + return // ... + } } ---- <1> `BeforeSaveCallback` receiving its order from the `@Order` annotation. <2> `BeforeSaveCallback` receiving its order via the `Ordered` interface implementation. <3> `BeforeSaveCallback` using a lambda expression. Unordered by default and invoked last. -<4> Combine multiple entity callback interfaces in one implementation class. +<4> Combine multiple entity callback interfaces in a single implementation class. ==== diff --git a/src/main/java/org/springframework/data/mapping/callback/EntityCallback.java b/src/main/java/org/springframework/data/mapping/callback/EntityCallback.java index a6eb2e2b7..0f1478a66 100644 --- a/src/main/java/org/springframework/data/mapping/callback/EntityCallback.java +++ b/src/main/java/org/springframework/data/mapping/callback/EntityCallback.java @@ -16,34 +16,47 @@ package org.springframework.data.mapping.callback; /** - * Marker interface for entity callbacks to be implemented in specific callback subtypes intended for internal usage - * within store specific implementations.
+ * Marker interface for entity callbacks to be implemented in specific callback subtypes. Intended for internal usage + * within store specific implementations. + *

Ordering {@link EntityCallback}

+ *

+ * Multiple entity callbacks are invoked sequentially with the result of the previous callback. Callbacks are unordered + * by default. It is possible to define the order in which listeners for a certain domain type are to be invoked. To do + * so, add Spring's common {@link org.springframework.core.annotation.Order @Order} annotation or implement + * {@link org.springframework.core.Ordered}. + *

Exception Handling

*

- * Multiple entity callbacks are invoked sequentially with the result of the previous callback. Those callbacks do by - * default not follow an explicit order of invocation. It is strongly recommended to enforce ordering for callbacks of - * the same by implementing {@link org.springframework.core.Ordered} or following the annotation driven approach using - * {@link org.springframework.core.annotation.Order}. + * While it is possible for a {@link EntityCallback} to declare that it throws arbitrary exception types, any checked + * exceptions thrown from a {@link EntityCallback} are wrapped in an + * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException} since the callback mechanism can + * only handle runtime exceptions. Entity callback processing is stopped on the {@link EntityCallback} that raised an + * exception and the caused exception is propagated to the caller. + *

Domain Type Binding

*

- * Entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}. + * An {@link EntityCallback} can generically declare the domain type that it is able to process by specifying the + * generic type parameter {@code }. When registered with a Spring + * {@link org.springframework.context.ApplicationContext}, callbacks are filtered accordingly, with the callback getting + * invoked for assignable domain objects only. + *

+ * Typically, entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}. + *

+ *

Defining {@link EntityCallback} Interfaces

*

- * A store specific {@link EntityCallback} needs to define a callback method accepting an object of the parameterized - * type as its first argument followed by additional optional arguments. - * - *

- * 
+ * A {@link EntityCallback} interface needs to define a callback method accepting an object of the parameterized type as
+ * its first argument followed by additional optional arguments.
+ *
+ * 
  *
  * public interface BeforeSaveCallback<T> extends EntityCallback<T> {
  *
- *     T onBeforeSave(T entity, String collection);
+ * 	T onBeforeSave(T entity, String collection);
  * }
- * 
  * 
* - * The - * * @author Mark Paluch * @author Christoph Strobl - * @param Entity type. Used to detect {@link EntityCallback callbacks} to invoke via their generic type signature. + * @param Entity type used to detect {@link EntityCallback callbacks} to invoke via their generic type signature. + * @since 2.2 * @see org.springframework.core.Ordered * @see org.springframework.core.annotation.Order */ diff --git a/src/main/java/org/springframework/data/mapping/callback/EntityCallbacks.java b/src/main/java/org/springframework/data/mapping/callback/EntityCallbacks.java index b65be1675..a7986ea17 100644 --- a/src/main/java/org/springframework/data/mapping/callback/EntityCallbacks.java +++ b/src/main/java/org/springframework/data/mapping/callback/EntityCallbacks.java @@ -19,8 +19,12 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.util.Assert; /** + * Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these + * with a specific entity. + * * @author Christoph Strobl * @since 2.2 + * @see EntityCallback */ public interface EntityCallbacks { @@ -39,7 +43,7 @@ public interface EntityCallbacks { * @param callbackType must not be {@literal null}. * @param entity must not be {@literal null}. * @param args optional arguments. - * @param + * @param Entity type. * @return never {@literal null}. * @throws IllegalArgumentException if a required argument is {@literal null}. */ diff --git a/src/main/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacks.java b/src/main/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacks.java index 7626844ee..93c7f6859 100644 --- a/src/main/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacks.java +++ b/src/main/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacks.java @@ -21,8 +21,12 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.util.Assert; /** + * Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these + * with a specific entity. + * * @author Christoph Strobl * @since 2.2 + * @see EntityCallback */ public interface ReactiveEntityCallbacks { @@ -42,7 +46,7 @@ public interface ReactiveEntityCallbacks { * @param callbackType must not be {@literal null}. * @param entity must not be {@literal null}. * @param args optional arguments. - * @param + * @param Entity type. * @return a {@link Mono} emitting the result after invoking the callbacks. * @throws IllegalArgumentException if a required argument is {@literal null}. */