) o);
- }
-
- throw new IllegalStateException("Callback " + callback + " returned a non-Publisher type " + o);
-
- });
- }
-
- return deferredCallbackChain;
+ Assert.notNull(beanFactory, "Context must not be null!");
+ return new DefaultReactiveEntityCallbacks(beanFactory);
}
}
diff --git a/src/main/java/org/springframework/data/mapping/callback/SimpleEntityCallbacks.java b/src/main/java/org/springframework/data/mapping/callback/SimpleEntityCallbacks.java
deleted file mode 100644
index a26ef9aca..000000000
--- a/src/main/java/org/springframework/data/mapping/callback/SimpleEntityCallbacks.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * 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
- *
- * https://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 org.springframework.data.mapping.callback;
-
-import java.util.function.BiFunction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.ResolvableType;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-import org.springframework.util.ErrorHandler;
-
-/**
- * Simple implementation to invoke {@link EntityCallback}s.
- *
- * @author Mark Paluch
- */
-public class SimpleEntityCallbacks extends AbstractEntityCallbacks {
-
- @Nullable private ErrorHandler errorHandler;
-
- public SimpleEntityCallbacks() {
- super();
- }
-
- public SimpleEntityCallbacks(ApplicationContext beanFactory) {
- super(beanFactory);
- }
-
- /**
- * Set the {@link ErrorHandler} to invoke in case an exception is thrown from a {@link EntityCallback}.
- *
- * Default is none, with a callback {@link Exception} stopping the current dispatch and getting propagated to the
- * publisher of the current event.
- *
- * Consider setting an {@link ErrorHandler} implementation that catches and logs exceptions (a la
- * {@link org.springframework.scheduling.support.TaskUtils#LOG_AND_SUPPRESS_ERROR_HANDLER}) or an implementation that
- * logs exceptions while nevertheless propagating them (e.g.
- * {@link org.springframework.scheduling.support.TaskUtils#LOG_AND_PROPAGATE_ERROR_HANDLER}).
- */
- public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
- this.errorHandler = errorHandler;
- }
-
- /**
- * Return the current {@link ErrorHandler} for this callback dispatcher.
- *
- * @return the current {@link ErrorHandler}.
- */
- @Nullable
- protected ErrorHandler getErrorHandler() {
- return this.errorHandler;
- }
-
- /**
- * Perform an entity callback.
- *
- * @param entity the entity, must not be {@literal null}.
- * @param callbackType desired callback type.
- * @param callbackInvoker invocation function for the callback to optionally pass additional parameters.
- * @return the resulting entity after invoking all callbacks.
- */
- @SuppressWarnings("unchecked")
- public > T callback(T entity, Class extends E> callbackType,
- BiFunction extends E, T, Object> callbackInvoker) {
-
- Assert.notNull(entity, "Entity must not be null!");
-
- ResolvableType resolvedCallbackType = ResolvableType.forClass(callbackType);
- T entityToUse = entity;
-
- for (EntityCallback> callback : getEntityCallbacks(entity, resolvedCallbackType)) {
- entityToUse = (T) invokeCallback(callback, entityToUse, (BiFunction) callbackInvoker);
- }
-
- return entityToUse;
- }
-
- /**
- * Invoke the given callback with the given entity.
- */
- protected Object invokeCallback(EntityCallback> callback, Object entity,
- BiFunction, Object, Object> callbackInvokerFunction) {
-
- ErrorHandler errorHandler = getErrorHandler();
-
- if (errorHandler != null) {
- try {
- return doInvokeCallback(callback, entity, callbackInvokerFunction);
- } catch (Throwable err) {
- errorHandler.handleError(err);
- return entity;
- }
- }
-
- return doInvokeCallback(callback, entity, callbackInvokerFunction);
-
- }
-
- @SuppressWarnings({ "rawtypes" })
- private Object doInvokeCallback(EntityCallback> callback, Object entity,
- BiFunction, Object, Object> callbackInvokerFunction) {
-
- try {
- return callbackInvokerFunction.apply(callback, entity);
- } catch (ClassCastException ex) {
- String msg = ex.getMessage();
- if (msg == null || matchesClassCastMessage(msg, entity.getClass())) {
- // Possibly a lambda-defined listener which we could not resolve the generic event type for
- // -> let's suppress the exception and just log a debug message.
- Log logger = LogFactory.getLog(getClass());
- if (logger.isDebugEnabled()) {
- logger.debug("Non-matching callback type for entity callback: " + callback, ex);
- }
-
- return entity;
- } else {
- throw ex;
- }
- }
- }
-
- private boolean matchesClassCastMessage(String classCastMessage, Class> eventClass) {
-
- // On Java 8, the message starts with the class name: "java.lang.String cannot be cast..."
- if (classCastMessage.startsWith(eventClass.getName())) {
- return true;
- }
-
- // On Java 11, the message starts with "class ..." a.k.a. Class.toString()
- if (classCastMessage.startsWith(eventClass.toString())) {
- return true;
- }
-
- // On Java 9, the message used to contain the module name: "java.base/java.lang.String cannot be cast..."
- int moduleSeparatorIndex = classCastMessage.indexOf('/');
- if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
- return true;
- }
-
- // Assuming an unrelated class cast failure...
- return false;
- }
-}
diff --git a/src/main/java/org/springframework/data/mapping/callback/package-info.java b/src/main/java/org/springframework/data/mapping/callback/package-info.java
new file mode 100644
index 000000000..ade249bfc
--- /dev/null
+++ b/src/main/java/org/springframework/data/mapping/callback/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Mapping callback API and implementation base classes.
+ */
+@org.springframework.lang.NonNullApi
+package org.springframework.data.mapping.callback;
diff --git a/src/test/java/org/springframework/data/mapping/callback/CapturingEntityCallback.java b/src/test/java/org/springframework/data/mapping/callback/CapturingEntityCallback.java
new file mode 100644
index 000000000..226012d77
--- /dev/null
+++ b/src/test/java/org/springframework/data/mapping/callback/CapturingEntityCallback.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.mapping.callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.core.Ordered;
+import org.springframework.data.mapping.Person;
+import org.springframework.data.mapping.PersonDocument;
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * @author Christoph Strobl
+ */
+class CapturingEntityCallback implements EntityCallback {
+
+ final List captured = new ArrayList<>(3);
+ final @Nullable Person returnValue;
+
+ CapturingEntityCallback() {
+ this(new PersonDocument(null, null, null));
+ }
+
+ CapturingEntityCallback(@Nullable Person returnValue) {
+ this.returnValue = returnValue;
+ }
+
+ public Person doSomething(Person person) {
+
+ captured.add(person);
+ return returnValue;
+ }
+
+ Person capturedValue() {
+ return CollectionUtils.lastElement(captured);
+ }
+
+ List capturedValues() {
+ return captured;
+ }
+
+ static class FirstCallback extends CapturingEntityCallback implements Ordered {
+
+ @Override
+ public int getOrder() {
+ return 1;
+ }
+ }
+
+ static class SecondCallback extends CapturingEntityCallback implements Ordered {
+
+ public SecondCallback() {}
+
+ public SecondCallback(Person returnValue) {
+ super(returnValue);
+ }
+
+ @Override
+ public int getOrder() {
+ return 2;
+ }
+ }
+
+ static class ThirdCallback extends CapturingEntityCallback implements Ordered {
+
+ @Override
+ public int getOrder() {
+ return 3;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java
new file mode 100644
index 000000000..b9f977163
--- /dev/null
+++ b/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.mapping.callback;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.Ordered;
+import org.springframework.data.mapping.Person;
+import org.springframework.data.mapping.PersonDocument;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.FirstCallback;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.SecondCallback;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.ThirdCallback;
+
+/**
+ * Unit tests for {@link DefaultEntityCallbacks}.
+ *
+ * @author Mark Paluch
+ * @author Christoph Strobl
+ */
+public class DefaultEntityCallbacksUnitTests {
+
+ @Test // DATACMNS-1467
+ public void shouldDispatchCallback() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks(ctx);
+
+ PersonDocument personDocument = new PersonDocument(null, "Walter", null);
+ PersonDocument afterCallback = callbacks.callback(BeforeSaveCallback.class, personDocument);
+
+ assertThat(afterCallback.getSsn()).isEqualTo(6);
+ }
+
+ @Test // DATACMNS-1467
+ public void shouldDispatchCallsToLambdaReceivers() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(LambdaConfig.class);
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks(ctx);
+
+ PersonDocument personDocument = new PersonDocument(null, "Walter", null);
+ PersonDocument afterCallback = callbacks.callback(BeforeSaveCallback.class, personDocument);
+
+ assertThat(afterCallback).isSameAs(personDocument);
+ }
+
+ @Test // DATACMNS-1467
+ public void invokeGenericEvent() {
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(new GenericPersonCallback());
+
+ Person afterCallback = callbacks.callback(GenericPersonCallback.class, new PersonDocument(null, "Walter", null));
+
+ assertThat(afterCallback.getSsn()).isEqualTo(6);
+ }
+
+ @Test // DATACMNS-1467
+ public void invokeGenericEventWithArgs() {
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(new GenericPersonCallbackWithArgs());
+
+ Person afterCallback = callbacks.callback(GenericPersonCallbackWithArgs.class,
+ new PersonDocument(null, "Walter", null), "agr0", Float.POSITIVE_INFINITY);
+
+ assertThat(afterCallback.getSsn()).isEqualTo(6);
+ }
+
+ @Test // DATACMNS-1467
+ public void invokeInvalidEvent() {
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(new InvalidEntityCallback() {});
+
+ assertThatExceptionOfType(IllegalStateException.class)
+ .isThrownBy(() -> callbacks.callback(InvalidEntityCallback.class, new PersonDocument(null, "Walter", null),
+ "agr0", Float.POSITIVE_INFINITY));
+ }
+
+ @Test // DATACMNS-1467
+ public void passesInvocationResultOnAlongTheChain() {
+
+ CapturingEntityCallback first = new FirstCallback();
+ CapturingEntityCallback second = new SecondCallback();
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(first);
+ callbacks.addEntityCallback(second);
+
+ PersonDocument initial = new PersonDocument(null, "Walter", null);
+
+ callbacks.callback(CapturingEntityCallback.class, initial);
+
+ assertThat(first.capturedValue()).isSameAs(initial);
+ assertThat(first.capturedValues()).hasSize(1);
+ assertThat(second.capturedValue()).isNotSameAs(initial);
+ assertThat(second.capturedValues()).hasSize(1);
+ }
+
+ @Test // DATACMNS-1467
+ public void errorsOnNullEntity() {
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(new CapturingEntityCallback());
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
+ }
+
+ @Test // DATACMNS-1467
+ public void errorsOnNullValueReturnedByCallbackEntity() {
+
+ CapturingEntityCallback first = new FirstCallback();
+ CapturingEntityCallback second = new SecondCallback(null);
+ CapturingEntityCallback third = new ThirdCallback();
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
+ callbacks.addEntityCallback(first);
+ callbacks.addEntityCallback(second);
+ callbacks.addEntityCallback(third);
+
+ PersonDocument initial = new PersonDocument(null, "Walter", null);
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
+
+ assertThat(first.capturedValue()).isSameAs(initial);
+ assertThat(second.capturedValue()).isNotNull().isNotSameAs(initial);
+ assertThat(third.capturedValues()).isEmpty();
+ }
+
+ @Test // DATACMNS-1467
+ public void detectsMultipleCallbacksWithinOneClass() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleCallbacksInOneClassConfig.class);
+
+ DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks(ctx);
+
+ PersonDocument personDocument = new PersonDocument(null, "Walter", null);
+ callbacks.callback(BeforeSaveCallback.class, personDocument);
+
+ assertThat(ctx.getBean("callbacks", MultipleCallbacks.class).invocations).containsExactly("save");
+
+ callbacks.callback(BeforeConvertCallback.class, personDocument);
+
+ assertThat(ctx.getBean("callbacks", MultipleCallbacks.class).invocations).containsExactly("save", "convert");
+ }
+
+ @Configuration
+ static class MyConfig {
+
+ @Bean
+ MyBeforeSaveCallback callback() {
+ return new MyBeforeSaveCallback();
+ }
+
+ @Bean
+ @Lazy
+ Object namedCallback() {
+ return new MyOtherCallback();
+ }
+ }
+
+ @Configuration
+ static class LambdaConfig {
+
+ @Bean
+ BeforeSaveCallback userCallback() {
+ return object -> object;
+ }
+
+ @Bean
+ BeforeSaveCallback personCallback() {
+
+ return object -> {
+ object.setSsn(object.getFirstName().length());
+ return object;
+ };
+ }
+ }
+
+ @Configuration
+ static class MultipleCallbacksInOneClassConfig {
+
+ @Bean
+ MultipleCallbacks callbacks() {
+ return new MultipleCallbacks();
+ }
+ }
+
+ interface BeforeConvertCallback extends EntityCallback, Ordered {
+ T onBeforeConvert(T object);
+
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+ }
+
+ interface BeforeSaveCallback extends EntityCallback {
+ T onBeforeSave(T object);
+ }
+
+ static class MyBeforeSaveCallback implements BeforeSaveCallback {
+
+ @Override
+ public Person onBeforeSave(Person object) {
+
+ object.setSsn(object.getFirstName().length());
+ return object;
+ }
+ }
+
+ static class MyOtherCallback implements BeforeSaveCallback {
+
+ @Override
+ public Person onBeforeSave(Person object) {
+ return object;
+ }
+ }
+
+ static class User {}
+
+ static class GenericPersonCallback implements EntityCallback {
+
+ public Person onBeforeSave(Person value) {
+
+ value.setSsn(value.getFirstName().length());
+ return value;
+ }
+ }
+
+ static class GenericPersonCallbackWithArgs implements EntityCallback {
+
+ public Person onBeforeSave(Person value, String agr1, Object arg2) {
+
+ value.setSsn(value.getFirstName().length());
+ return value;
+ }
+ }
+
+ interface InvalidEntityCallback extends EntityCallback {
+
+ default Person onBeforeSave(String value, Person entity) {
+ return entity;
+ }
+ }
+
+ static class MultipleCallbacks implements BeforeConvertCallback, BeforeSaveCallback {
+
+ List invocations = new ArrayList(2);
+
+ @Override
+ public Person onBeforeConvert(Person object) {
+
+ invocations.add("convert");
+ return object;
+ }
+
+ @Override
+ public Person onBeforeSave(Person object) {
+
+ invocations.add("save");
+ return object;
+ }
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacksUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacksUnitTests.java
new file mode 100644
index 000000000..644de4ec1
--- /dev/null
+++ b/src/test/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacksUnitTests.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.mapping.callback;
+
+import static org.assertj.core.api.Assertions.*;
+
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mapping.Person;
+import org.springframework.data.mapping.PersonDocument;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.FirstCallback;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.SecondCallback;
+import org.springframework.data.mapping.callback.CapturingEntityCallback.ThirdCallback;
+
+/**
+ * Unit tests for {@link DefaultReactiveEntityCallbacks}.
+ *
+ * @author Mark Paluch
+ * @author Christoph Strobl
+ */
+public class DefaultReactiveEntityCallbacksUnitTests {
+
+ @Test // DATACMNS-1467
+ public void dispatchResolvesOnSubscribe() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
+
+ DefaultReactiveEntityCallbacks callbacks = new DefaultReactiveEntityCallbacks(ctx);
+
+ PersonDocument personDocument = new PersonDocument(null, "Walter", null);
+ Mono afterCallback = callbacks.callback(ReactiveBeforeSaveCallback.class, personDocument);
+
+ assertThat(personDocument.getSsn()).isNull();
+
+ afterCallback.as(StepVerifier::create) //
+ .consumeNextWith(it -> assertThat(it.getSsn()).isEqualTo(6)) //
+ .verifyComplete();
+ }
+
+ @Test // DATACMNS-1467
+ public void invokeGenericEvent() {
+
+ DefaultReactiveEntityCallbacks callbacks = new DefaultReactiveEntityCallbacks();
+ callbacks.addEntityCallback(new GenericPersonCallback());
+
+ callbacks.callback(GenericPersonCallback.class, new PersonDocument(null, "Walter", null)) //
+ .as(StepVerifier::create) //
+ .consumeNextWith(it -> assertThat(it.getSsn()).isEqualTo(6)) //
+ .verifyComplete();
+ }
+
+ @Test // DATACMNS-1467
+ public void passesInvocationResultOnAlongTheChain() {
+
+ CapturingEntityCallback first = new FirstCallback();
+ CapturingEntityCallback second = new SecondCallback();
+
+ DefaultReactiveEntityCallbacks callbacks = new DefaultReactiveEntityCallbacks();
+ callbacks.addEntityCallback(first);
+ callbacks.addEntityCallback(second);
+
+ PersonDocument initial = new PersonDocument(null, "Walter", null);
+
+ callbacks.callback(CapturingEntityCallback.class, initial) //
+ .as(StepVerifier::create) //
+ .expectNextCount(1) //
+ .verifyComplete();
+
+ assertThat(first.capturedValue()).isSameAs(initial);
+ assertThat(first.capturedValues()).hasSize(1);
+ assertThat(second.capturedValue()).isNotSameAs(initial);
+ assertThat(second.capturedValues()).hasSize(1);
+ }
+
+ @Test // DATACMNS-1467
+ public void errorsOnNullEntity() {
+
+ DefaultReactiveEntityCallbacks callbacks = new DefaultReactiveEntityCallbacks();
+ callbacks.addEntityCallback(new CapturingEntityCallback());
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
+ }
+
+ @Test // DATACMNS-1467
+ public void errorsOnNullValueReturnedByCallbackEntity() {
+
+ CapturingEntityCallback first = new FirstCallback();
+ CapturingEntityCallback second = new SecondCallback(null);
+ CapturingEntityCallback third = new ThirdCallback();
+
+ DefaultReactiveEntityCallbacks callbacks = new DefaultReactiveEntityCallbacks();
+ callbacks.addEntityCallback(first);
+ callbacks.addEntityCallback(second);
+ callbacks.addEntityCallback(third);
+
+ PersonDocument initial = new PersonDocument(null, "Walter", null);
+
+ callbacks.callback(CapturingEntityCallback.class, initial) //
+ .as(StepVerifier::create) //
+ .expectError(IllegalArgumentException.class) //
+ .verify();
+
+ assertThat(first.capturedValue()).isSameAs(initial);
+ assertThat(second.capturedValue()).isNotNull().isNotSameAs(initial);
+ assertThat(third.capturedValues()).isEmpty();
+ }
+
+ @Configuration
+ static class MyConfig {
+
+ @Bean
+ MyReactiveBeforeSaveCallback callback() {
+ return new MyReactiveBeforeSaveCallback();
+ }
+
+ }
+
+ interface ReactiveBeforeSaveCallback extends EntityCallback {
+ Mono onBeforeSave(T object);
+ }
+
+ static class MyReactiveBeforeSaveCallback implements ReactiveBeforeSaveCallback {
+
+ @Override
+ public Mono onBeforeSave(Person object) {
+
+ PersonDocument result = new PersonDocument(object.getFirstName().length(), object.getFirstName(),
+ object.getLastName());
+
+ return Mono.just(result);
+ }
+ }
+
+ static class GenericPersonCallback implements EntityCallback {
+
+ public Person onBeforeSave(Person value) {
+
+ value.setSsn(value.getFirstName().length());
+ return value;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java
new file mode 100644
index 000000000..eead4aa04
--- /dev/null
+++ b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.mapping.callback;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collection;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.Ordered;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.mapping.Child;
+import org.springframework.data.mapping.Person;
+import org.springframework.data.mapping.PersonDocument;
+
+/**
+ * @author Christoph Strobl
+ */
+public class EntityCallbackDiscovererUnitTests {
+
+ @Test // DATACMNS-1467
+ public void shouldDiscoverCallbackType() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
+
+ EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer(ctx);
+
+ Collection> entityCallbacks = discoverer.getEntityCallbacks(PersonDocument.class,
+ ResolvableType.forType(BeforeSaveCallback.class));
+
+ assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyBeforeSaveCallback.class);
+ }
+
+ @Test // DATACMNS-1467
+ public void shouldDiscoverCallbackTypeByName() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
+
+ EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer(ctx);
+ discoverer.clear();
+ discoverer.addEntityCallbackBean("namedCallback");
+
+ Collection> entityCallbacks = discoverer.getEntityCallbacks(PersonDocument.class,
+ ResolvableType.forType(BeforeSaveCallback.class));
+
+ assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyOtherCallback.class);
+ }
+
+ @Test // DATACMNS-1467
+ public void shouldSupportCallbackTypes() {
+
+ EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer();
+
+ assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Person.class))).isTrue();
+ assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Child.class))).isTrue();
+ assertThat(discoverer.supportsEvent(BeforeSaveCallback.class, ResolvableType.forClass(PersonDocument.class)))
+ .isTrue();
+
+ assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Object.class))).isFalse();
+ assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(User.class))).isFalse();
+ }
+
+ @Test // DATACMNS-1467
+ public void shouldSupportInstanceCallbackTypes() {
+
+ EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer();
+
+ MyBeforeSaveCallback callback = new MyBeforeSaveCallback();
+
+ assertThat(discoverer.supportsEvent(callback, ResolvableType.forClass(Person.class),
+ ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
+ assertThat(discoverer.supportsEvent(callback, ResolvableType.forClass(Child.class),
+ ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
+ assertThat(discoverer.supportsEvent(callback, ResolvableType.forClass(PersonDocument.class),
+ ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
+ assertThat(discoverer.supportsEvent(callback, ResolvableType.forClass(PersonDocument.class),
+ ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
+
+ assertThat(discoverer.supportsEvent(callback, ResolvableType.forClass(User.class),
+ ResolvableType.forClass(BeforeSaveCallback.class))).isFalse();
+ }
+
+ @Test // DATACMNS-1467
+ public void shouldDispatchInOrder() {
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(OrderedConfig.class);
+
+ EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer(ctx);
+
+ Collection> entityCallbacks = discoverer.getEntityCallbacks(PersonDocument.class,
+ ResolvableType.forType(EntityCallback.class));
+
+ assertThat(entityCallbacks).containsExactly(ctx.getBean("callback1", EntityCallback.class),
+ ctx.getBean("callback2", EntityCallback.class), ctx.getBean("callback3", EntityCallback.class),
+ ctx.getBean("callback4", EntityCallback.class));
+ }
+
+ @Configuration
+ static class MyConfig {
+
+ @Bean
+ MyBeforeSaveCallback callback() {
+ return new MyBeforeSaveCallback();
+ }
+
+ @Bean
+ @Lazy
+ Object namedCallback() {
+ return new MyOtherCallback();
+ }
+ }
+
+ @Configuration
+ static class OrderedConfig {
+
+ @Bean
+ EntityCallback callback4() {
+
+ return (BeforeSaveCallback) object -> object;
+ }
+
+ @Bean
+ EntityCallback callback2() {
+ return new Second();
+ }
+
+ @Bean
+ EntityCallback callback3() {
+ return new Third();
+ }
+
+ @Bean
+ EntityCallback callback1() {
+ return new First();
+ }
+
+ @Order(3)
+ static class Third implements EntityCallback {
+
+ public Person beforeSave(Person object) {
+ return object;
+ }
+ }
+
+ static class Second implements EntityCallback, Ordered {
+
+ public Person beforeSave(Person object) {
+ return object;
+ }
+
+ @Override
+ public int getOrder() {
+ return 2;
+ }
+ }
+
+ @Order(1)
+ static class First implements EntityCallback {
+
+ public Person beforeSave(Person object) {
+ return object;
+ }
+ }
+ }
+
+ interface BeforeSaveCallback extends EntityCallback {
+ T onBeforeSave(T object);
+ }
+
+ static class MyBeforeSaveCallback implements BeforeSaveCallback {
+
+ @Override
+ public Person onBeforeSave(Person object) {
+
+ object.setSsn(object.getFirstName().length());
+ return object;
+ }
+ }
+
+ static class MyOtherCallback implements BeforeSaveCallback {
+
+ @Override
+ public Person onBeforeSave(Person object) {
+ return object;
+ }
+ }
+
+ static class User {}
+}
diff --git a/src/test/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacksUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacksUnitTests.java
deleted file mode 100644
index f45156b99..000000000
--- a/src/test/java/org/springframework/data/mapping/callback/ReactiveEntityCallbacksUnitTests.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * 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
- *
- * https://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 org.springframework.data.mapping.callback;
-
-import static org.assertj.core.api.Assertions.*;
-
-import reactor.core.publisher.Mono;
-
-import org.junit.Test;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.mapping.Person;
-import org.springframework.data.mapping.PersonDocument;
-
-/**
- * Unit tests for {@link ReactiveEntityCallbacks}.
- *
- * @author Mark Paluch
- */
-public class ReactiveEntityCallbacksUnitTests {
-
- @Test
- public void shouldDispatchCallback() {
-
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
-
- ReactiveEntityCallbacks callbacks = new ReactiveEntityCallbacks(ctx);
-
- PersonDocument personDocument = new PersonDocument(null, "Walter", null);
- Mono afterCallback = callbacks.callbackLater(personDocument, ReactiveBeforeSaveCallback.class,
- ReactiveBeforeSaveCallback::onBeforeSave);
-
- assertThat(personDocument.getSsn()).isNull();
- assertThat(afterCallback.block().getSsn()).isEqualTo(6);
- }
-
- @Configuration
- static class MyConfig {
-
- @Bean
- MyReactiveBeforeSaveCallback callback() {
- return new MyReactiveBeforeSaveCallback();
- }
-
- }
-
- static class MyReactiveBeforeSaveCallback implements ReactiveBeforeSaveCallback {
-
- @Override
- public Mono onBeforeSave(Person object) {
-
- PersonDocument result = new PersonDocument(object.getFirstName().length(), object.getFirstName(),
- object.getLastName());
-
- return Mono.just(result);
- }
- }
-}
diff --git a/src/test/java/org/springframework/data/mapping/callback/SimpleEntityCallbacksUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/SimpleEntityCallbacksUnitTests.java
deleted file mode 100644
index 8d8cb5fd6..000000000
--- a/src/test/java/org/springframework/data/mapping/callback/SimpleEntityCallbacksUnitTests.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * 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
- *
- * https://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 org.springframework.data.mapping.callback;
-
-import static org.assertj.core.api.Assertions.*;
-
-import java.util.Collection;
-
-import org.junit.Test;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.core.ResolvableType;
-import org.springframework.data.mapping.Child;
-import org.springframework.data.mapping.Person;
-import org.springframework.data.mapping.PersonDocument;
-
-/**
- * Unit tests for {@link SimpleEntityCallbacks}.
- *
- * @author Mark Paluch
- */
-public class SimpleEntityCallbacksUnitTests {
-
- @Test
- public void shouldDispatchCallback() {
-
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks(ctx);
-
- PersonDocument personDocument = new PersonDocument(null, "Walter", null);
- PersonDocument afterCallback = callbacks.callback(personDocument, BeforeSaveCallback.class,
- BeforeSaveCallback::onBeforeSave);
-
- assertThat(afterCallback.getSsn()).isEqualTo(6);
- }
-
- @Test
- public void shouldDispatchCallsToLambdaReceivers() {
-
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(LambdaConfig.class);
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks(ctx);
-
- PersonDocument personDocument = new PersonDocument(null, "Walter", null);
- PersonDocument afterCallback = callbacks.callback(personDocument, BeforeSaveCallback.class,
- BeforeSaveCallback::onBeforeSave);
-
- assertThat(afterCallback.getSsn()).isEqualTo(6);
- }
-
- @Test
- public void shouldDiscoverCallbackType() {
-
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks(ctx);
-
- Collection> entityCallbacks = callbacks.getEntityCallbacks(new PersonDocument(null, null, null),
- ResolvableType.forType(BeforeSaveCallback.class));
-
- assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyBeforeSaveCallback.class);
- }
-
- @Test
- public void shouldDiscoverCallbackTypeByName() {
-
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks(ctx);
- callbacks.removeAllCallbacks();
- callbacks.addEntityCallbackBean("namedCallback");
-
- Collection> entityCallbacks = callbacks.getEntityCallbacks(new PersonDocument(null, null, null),
- ResolvableType.forType(BeforeSaveCallback.class));
-
- assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyOtherCallback.class);
- }
-
- @Test
- public void shouldSupportCallbackTypes() {
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks();
-
- assertThat(callbacks.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Person.class))).isTrue();
- assertThat(callbacks.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Child.class))).isTrue();
- assertThat(callbacks.supportsEvent(BeforeSaveCallback.class, ResolvableType.forClass(PersonDocument.class)))
- .isTrue();
-
- assertThat(callbacks.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Object.class))).isFalse();
- assertThat(callbacks.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(User.class))).isFalse();
- }
-
- @Test
- public void shouldSupportInstanceCallbackTypes() {
-
- SimpleEntityCallbacks callbacks = new SimpleEntityCallbacks();
-
- MyBeforeSaveCallback callback = new MyBeforeSaveCallback();
-
- assertThat(callbacks.supportsEvent(callback, ResolvableType.forClass(Person.class),
- ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
- assertThat(callbacks.supportsEvent(callback, ResolvableType.forClass(Child.class),
- ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
- assertThat(callbacks.supportsEvent(callback, ResolvableType.forClass(PersonDocument.class),
- ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
- assertThat(callbacks.supportsEvent(callback, ResolvableType.forClass(PersonDocument.class),
- ResolvableType.forClass(BeforeSaveCallback.class))).isTrue();
-
- assertThat(callbacks.supportsEvent(callback, ResolvableType.forClass(User.class),
- ResolvableType.forClass(BeforeSaveCallback.class))).isFalse();
- }
-
- @Configuration
- static class MyConfig {
-
- @Bean
- MyBeforeSaveCallback callback() {
- return new MyBeforeSaveCallback();
- }
-
- @Bean
- @Lazy
- Object namedCallback() {
- return new MyOtherCallback();
- }
- }
-
- @Configuration
- static class LambdaConfig {
-
- @Bean
- BeforeSaveCallback userCallback() {
- return object -> object;
- }
-
- @Bean
- BeforeSaveCallback personCallback() {
- return object -> {
- object.setSsn(object.getFirstName().length());
- return object;
- };
- }
- }
-
- static class MyBeforeSaveCallback implements BeforeSaveCallback {
-
- @Override
- public Person onBeforeSave(Person object) {
- object.setSsn(object.getFirstName().length());
- return object;
- }
- }
-
- static class MyOtherCallback implements BeforeSaveCallback {
-
- @Override
- public Person onBeforeSave(Person object) {
- return object;
- }
- }
-
- static class User {}
-}