DATACMNS-1467 - Entity Callback API draft.
This is a draft for a possible Entity Callback API heavily inspired by Spring Framework's Application Event listeners. Entity callbacks are callbacks for entities that allow for entity modification at certain check points such as before saving an entity or after loading it.
Entity callbacks consist of a marker-interface for all entity callback types and the EntityCallbacks API to dispatch callbacks.
Entity callbacks are picked up from the ApplicationContext and invoked sequentially according to their ordering.
Usage example:
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);
Mono<PersonDocument> afterCallback = callbacks.callbackLater(personDocument, ReactiveBeforeSaveCallback.class,
ReactiveBeforeSaveCallback::onBeforeSave);
@Configuration
static class MyConfig {
@Bean
BeforeSaveCallback<Person> personCallback() {
return object -> {
object.setSsn(object.getFirstName().length());
return object;
};
}
@Bean
MyReactiveBeforeSaveCallback callback() {
return new MyReactiveBeforeSaveCallback();
}
}
class MyReactiveBeforeSaveCallback implements ReactiveBeforeSaveCallback<Person> {
@Override
public Mono<Person> onBeforeSave(Person object) {
PersonDocument result = new PersonDocument(object.getFirstName().length(), object.getFirstName(),
object.getLastName());
return Mono.just(result);
}
}
Original Pull Request: #332
This commit is contained in:
committed by
Christoph Strobl
parent
83611de861
commit
52724cd5dd
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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<PersonDocument> 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<Person> {
|
||||
|
||||
@Override
|
||||
public Mono<Person> onBeforeSave(Person object) {
|
||||
|
||||
PersonDocument result = new PersonDocument(object.getFirstName().length(), object.getFirstName(),
|
||||
object.getLastName());
|
||||
|
||||
return Mono.just(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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<EntityCallback<?>> 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<EntityCallback<?>> 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<User> userCallback() {
|
||||
return object -> object;
|
||||
}
|
||||
|
||||
@Bean
|
||||
BeforeSaveCallback<Person> personCallback() {
|
||||
return object -> {
|
||||
object.setSsn(object.getFirstName().length());
|
||||
return object;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class MyBeforeSaveCallback implements BeforeSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onBeforeSave(Person object) {
|
||||
object.setSsn(object.getFirstName().length());
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
static class MyOtherCallback implements BeforeSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onBeforeSave(Person object) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
static class User {}
|
||||
}
|
||||
Reference in New Issue
Block a user