Browse Source

Improve lambda-based EntityCallback detection.

In case an EntityCallback is declared as lambda expression, the JVM does not expose any generics information about the target entity type the callback shall be applied to. This commit changes the callback lookup and processing so that in case the generics information is not detectable on the type, we fall back to the BeanDefinition's resolvable type (fed by the factory method's return type which carries the necessary reflection information). That generics information is then kept in the newly introduce EntityCallbackAdapter and the code inspecting the actual entity type for matches then uses the resolvable type held in that. Also, the actual callback invocation is done on the adapter's delegate.

Removed the ability of the discoverer to register EntityCallbacks by bean name as that was not used in the public API at all and it avoids duplicating the bean definition type detection. A couple of minor additional cleanups (records for cache key, methods static where possible and with lower visibility etc.)

Fixes #2812.
pull/2823/head
Oliver Drotbohm 3 years ago
parent
commit
83162b25c7
  1. 8
      src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java
  2. 8
      src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java
  3. 290
      src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java
  4. 7
      src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java
  5. 34
      src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java

8
src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java

@ -21,7 +21,6 @@ import java.util.function.BiFunction; @@ -21,7 +21,6 @@ import java.util.function.BiFunction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
@ -35,6 +34,7 @@ import org.springframework.util.ReflectionUtils; @@ -35,6 +34,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Oliver Drotbohm
* @since 2.2
*/
class DefaultEntityCallbacks implements EntityCallbacks {
@ -93,7 +93,8 @@ class DefaultEntityCallbacks implements EntityCallbacks { @@ -93,7 +93,8 @@ class DefaultEntityCallbacks implements EntityCallbacks {
this.callbackDiscoverer.addEntityCallback(callback);
}
static class SimpleEntityCallbackInvoker implements org.springframework.data.mapping.callback.EntityCallbackInvoker {
static class SimpleEntityCallbackInvoker
implements org.springframework.data.mapping.callback.EntityCallbackInvoker {
@Override
public <T> T invokeCallback(EntityCallback<T> callback, T entity,
@ -108,11 +109,12 @@ class DefaultEntityCallbacks implements EntityCallbacks { @@ -108,11 +109,12 @@ class DefaultEntityCallbacks implements EntityCallbacks {
}
throw new IllegalArgumentException(
String.format("Callback invocation on %s returned null value for %s", callback.getClass(), entity));
"Callback invocation on %s returned null value for %s".formatted(callback.getClass(), entity));
} catch (IllegalArgumentException | ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for

8
src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java

@ -24,7 +24,6 @@ import java.util.function.BiFunction; @@ -24,7 +24,6 @@ import java.util.function.BiFunction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
@ -38,6 +37,7 @@ import org.springframework.util.ReflectionUtils; @@ -38,6 +37,7 @@ import org.springframework.util.ReflectionUtils;
* @author Mark Paluch
* @author Christoph Strobl
* @author Michael J. Simons
* @author Oliver Drotbohm
*/
class DefaultReactiveEntityCallbacks implements ReactiveEntityCallbacks {
@ -63,11 +63,13 @@ class DefaultReactiveEntityCallbacks implements ReactiveEntityCallbacks { @@ -63,11 +63,13 @@ class DefaultReactiveEntityCallbacks implements ReactiveEntityCallbacks {
}
@Override
@SuppressWarnings("unchecked")
public <T> Mono<T> callback(Class<? extends EntityCallback> callbackType, T entity, Object... args) {
Assert.notNull(entity, "Entity must not be null");
Class<T> entityType = (Class<T>) (entity != null ? ClassUtils.getUserClass(entity.getClass())
Class<T> entityType = (Class<T>) (entity != null
? ClassUtils.getUserClass(entity.getClass())
: callbackDiscoverer.resolveDeclaredEntityType(callbackType).getRawClass());
Method callbackMethod = callbackMethodCache.computeIfAbsent(callbackType, it -> {
@ -112,7 +114,7 @@ class DefaultReactiveEntityCallbacks implements ReactiveEntityCallbacks { @@ -112,7 +114,7 @@ class DefaultReactiveEntityCallbacks implements ReactiveEntityCallbacks {
}
throw new IllegalArgumentException(
String.format("Callback invocation on %s returned null value for %s", callback.getClass(), entity));
"Callback invocation on %s returned null value for %s".formatted(callback.getClass(), entity));
} catch (IllegalArgumentException | ClassCastException ex) {
String msg = ex.getMessage();

290
src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java

@ -28,15 +28,18 @@ import java.util.function.BiFunction; @@ -28,15 +28,18 @@ import java.util.function.BiFunction;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.comparator.Comparators;
@ -45,11 +48,12 @@ import org.springframework.util.comparator.Comparators; @@ -45,11 +48,12 @@ import org.springframework.util.comparator.Comparators;
* @author Christoph Strobl
* @author Myeonghyeon Lee
* @author Johannes Englmeier
* @author Oliver Drotbohm
* @since 2.2
*/
class EntityCallbackDiscoverer {
private final CallbackRetriever defaultRetriever = new CallbackRetriever(false);
private final CallbackRetriever defaultRetriever = new CallbackRetriever();
private final Map<CallbackCacheKey, CallbackRetriever> retrieverCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, ResolvableType> entityTypeCache = new ConcurrentReferenceHashMap<>(64);
@ -64,9 +68,8 @@ class EntityCallbackDiscoverer { @@ -64,9 +68,8 @@ class EntityCallbackDiscoverer {
EntityCallbackDiscoverer() {}
/**
* Create a new {@link EntityCallback} instance.
* <p>
* Pre loads {@link EntityCallback} beans by scanning the {@link BeanFactory}.
* Create a new {@link EntityCallback} instance. <p Pre-loads {@link EntityCallback} beans by scanning the
* {@link BeanFactory}.
*/
EntityCallbackDiscoverer(BeanFactory beanFactory) {
setBeanFactory(beanFactory);
@ -81,18 +84,12 @@ class EntityCallbackDiscoverer { @@ -81,18 +84,12 @@ class EntityCallbackDiscoverer {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same callback.
Object singletonTarget = AopProxyUtils.getSingletonTarget(callback);
if (singletonTarget instanceof EntityCallback) {
this.defaultRetriever.entityCallbacks.remove(singletonTarget);
}
this.defaultRetriever.entityCallbacks.add(callback);
this.retrieverCache.clear();
}
}
void addEntityCallbackBean(String callbackBeanName) {
synchronized (this.retrievalMutex) {
this.defaultRetriever.entityCallbackBeans.add(callbackBeanName);
this.defaultRetriever.entityCallbacks.add(callback);
this.retrieverCache.clear();
}
}
@ -105,29 +102,12 @@ class EntityCallbackDiscoverer { @@ -105,29 +102,12 @@ class EntityCallbackDiscoverer {
}
}
void removeEntityCallbackBean(String callbackBeanName) {
synchronized (this.retrievalMutex) {
this.defaultRetriever.entityCallbackBeans.remove(callbackBeanName);
this.retrieverCache.clear();
}
}
void clear() {
synchronized (this.retrievalMutex) {
this.defaultRetriever.entityCallbacks.clear();
this.defaultRetriever.entityCallbackBeans.clear();
this.retrieverCache.clear();
}
}
/**
* Return a {@link Collection} of all {@link EntityCallback}s matching the given entity type. Non-matching callbacks
* get excluded early.
*
* @param entity the entity to be called back for. Allows for excluding non-matching callbacks early, based on cached
* matching information.
* @param entity the entity to be called back for. Allows for excluding non-matching callbacks early, based on
* cached matching information.
* @param callbackType the source callback type.
* @return a {@link Collection} of {@link EntityCallback}s.
* @see EntityCallback
@ -140,27 +120,27 @@ class EntityCallbackDiscoverer { @@ -140,27 +120,27 @@ class EntityCallbackDiscoverer {
// Quick check for existing entry on ConcurrentHashMap...
CallbackRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return (Collection<EntityCallback<S>>) (Collection) retriever.getEntityCallbacks();
return (Collection) retriever.getEntityCallbacks();
}
if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(entity.getClass(), this.beanClassLoader)
&& (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
if (this.beanClassLoader == null || ClassUtils.isCacheSafe(entity.getClass(), this.beanClassLoader)
&& (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader))) {
// Fully synchronized building and caching of a CallbackRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return (Collection<EntityCallback<S>>) (Collection) retriever.getEntityCallbacks();
return (Collection) retriever.getEntityCallbacks();
}
retriever = new CallbackRetriever(true);
retriever = new CallbackRetriever();
Collection<EntityCallback<?>> callbacks = retrieveEntityCallbacks(ResolvableType.forClass(sourceType),
callbackType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return (Collection<EntityCallback<S>>) (Collection) callbacks;
return (Collection) callbacks;
}
} else {
// No CallbackRetriever caching -> no synchronization necessary
return (Collection<EntityCallback<S>>) (Collection) retrieveEntityCallbacks(callbackType, callbackType, null);
return (Collection) retrieveEntityCallbacks(callbackType, callbackType, null);
}
}
@ -174,7 +154,7 @@ class EntityCallbackDiscoverer { @@ -174,7 +154,7 @@ class EntityCallbackDiscoverer {
entityTypeCache.put(callbackType, eventType);
}
return (eventType != ResolvableType.NONE ? eventType : null);
return eventType != ResolvableType.NONE ? eventType : null;
}
/**
@ -185,55 +165,33 @@ class EntityCallbackDiscoverer { @@ -185,55 +165,33 @@ class EntityCallbackDiscoverer {
* @param retriever the {@link CallbackRetriever}, if supposed to populate one (for caching purposes)
* @return the pre-filtered list of entity callbacks for the given entity and callback type.
*/
private Collection<EntityCallback<?>> retrieveEntityCallbacks(ResolvableType entityType, ResolvableType callbackType,
@Nullable CallbackRetriever retriever) {
private Collection<EntityCallback<?>> retrieveEntityCallbacks(ResolvableType entityType,
ResolvableType callbackType, @Nullable CallbackRetriever retriever) {
List<EntityCallback<?>> allCallbacks = new ArrayList<>();
Set<EntityCallback<?>> callbacks;
Set<String> callbackBeans;
synchronized (this.retrievalMutex) {
callbacks = new LinkedHashSet<>(this.defaultRetriever.entityCallbacks);
callbackBeans = new LinkedHashSet<>(this.defaultRetriever.entityCallbackBeans);
}
for (EntityCallback<?> callback : callbacks) {
if (supportsEvent(callback, entityType, callbackType)) {
callback = callback instanceof EntityCallbackAdapter<?> adapter ? adapter.delegate() : callback;
if (retriever != null) {
retriever.getEntityCallbacks().add(callback);
}
allCallbacks.add(callback);
}
}
if (!callbackBeans.isEmpty()) {
BeanFactory beanFactory = getRequiredBeanFactory();
for (String callbackBeanName : callbackBeans) {
try {
Class<?> callbackImplType = beanFactory.getType(callbackBeanName);
if (callbackImplType == null || supportsEvent(callbackImplType, entityType)) {
EntityCallback<?> callback = beanFactory.getBean(callbackBeanName, EntityCallback.class);
if (!allCallbacks.contains(callback) && supportsEvent(callback, entityType, callbackType)) {
if (retriever != null) {
if (beanFactory.isSingleton(callbackBeanName)) {
retriever.entityCallbacks.add(callback);
} else {
retriever.entityCallbackBeans.add(callbackBeanName);
}
}
allCallbacks.add(callback);
}
}
} catch (NoSuchBeanDefinitionException ex) {
// Singleton callback instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
allCallbacks.add(callback);
}
}
AnnotationAwareOrderComparator.sort(allCallbacks);
if (retriever != null && retriever.entityCallbackBeans.isEmpty()) {
if (retriever != null) {
retriever.entityCallbacks.clear();
retriever.entityCallbacks.addAll(allCallbacks);
}
@ -242,43 +200,8 @@ class EntityCallbackDiscoverer { @@ -242,43 +200,8 @@ class EntityCallbackDiscoverer {
}
/**
* Filter a callback early through checking its generically declared entity type before trying to instantiate it.
* <p>
* If this method returns {@literal true} for a given callback as a first pass, the callback instance will get
* retrieved and fully evaluated through a {@link #supportsEvent(EntityCallback, ResolvableType, ResolvableType)} call
* afterwards.
*
* @param callback the callback's type as determined by the BeanFactory.
* @param entityType the entity type to check.
* @return whether the given callback should be included in the candidates for the given callback type.
*/
protected boolean supportsEvent(Class<?> callback, ResolvableType entityType) {
ResolvableType declaredEventType = resolveDeclaredEntityType(callback);
return (declaredEventType == null || declaredEventType.isAssignableFrom(entityType));
}
/**
* Determine whether the given callback supports the given entity type and callback type.
*
* @param callback the target callback to check.
* @param entityType the entity type to check.
* @param callbackType the source type to check against.
* @return whether the given callback should be included in the candidates for the given callback type.
*/
protected boolean supportsEvent(EntityCallback<?> callback, ResolvableType entityType, ResolvableType callbackType) {
return supportsEvent(callback.getClass(), entityType)
&& callbackType.isAssignableFrom(ResolvableType.forInstance(callback));
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
/**
* Set the {@link BeanFactory} and optionally {@link #setBeanClassLoader(ClassLoader) class loader} if not set. Pre
* loads {@link EntityCallback} beans by scanning the {@link BeanFactory}.
* Set the {@link BeanFactory} and optionally {@link #setBeanClassLoader(ClassLoader) class loader} if not set.
* Pre-loads {@link EntityCallback} beans by scanning the {@link BeanFactory}.
*
* @param beanFactory must not be {@literal null}.
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory)
@ -288,13 +211,15 @@ class EntityCallbackDiscoverer { @@ -288,13 +211,15 @@ class EntityCallbackDiscoverer {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableBeanFactory cbf) {
if (this.beanClassLoader == null) {
this.beanClassLoader = cbf.getBeanClassLoader();
}
this.retrievalMutex = cbf.getSingletonMutex();
}
defaultRetriever.discoverEntityCallbacks(this.beanFactory);
defaultRetriever.discoverEntityCallbacks(beanFactory);
this.retrieverCache.clear();
}
@ -305,8 +230,10 @@ class EntityCallbackDiscoverer { @@ -305,8 +230,10 @@ class EntityCallbackDiscoverer {
ReflectionUtils.doWithMethods(callbackType, methods::add, method -> {
if (!Modifier.isPublic(method.getModifiers()) || method.getParameterCount() != args.length + 1
|| method.isBridge() || ReflectionUtils.isObjectMethod(method)) {
if (!Modifier.isPublic(method.getModifiers())
|| method.getParameterCount() != args.length + 1
|| method.isBridge()
|| ReflectionUtils.isObjectMethod(method)) {
return false;
}
@ -318,7 +245,7 @@ class EntityCallbackDiscoverer { @@ -318,7 +245,7 @@ class EntityCallbackDiscoverer {
}
throw new IllegalStateException(
String.format("%s does not define a callback method accepting %s and %s additional arguments",
"%s does not define a callback method accepting %s and %s additional arguments".formatted(
ClassUtils.getShortName(callbackType), ClassUtils.getShortName(entityType), args.length));
}
@ -329,6 +256,7 @@ class EntityCallbackDiscoverer { @@ -329,6 +256,7 @@ class EntityCallbackDiscoverer {
Object[] invocationArgs = new Object[args.length + 1];
invocationArgs[0] = entity;
if (args.length > 0) {
System.arraycopy(args, 0, invocationArgs, 1, args.length);
}
@ -337,104 +265,108 @@ class EntityCallbackDiscoverer { @@ -337,104 +265,108 @@ class EntityCallbackDiscoverer {
};
}
private BeanFactory getRequiredBeanFactory() {
/**
* Filter a callback early through checking its generically declared entity type before trying to instantiate it.
* <p>
* If this method returns {@literal true} for a given callback as a first pass, the callback instance will get
* retrieved and fully evaluated through a {@link #supportsEvent(EntityCallback, ResolvableType, ResolvableType)}
* call afterwards.
*
* @param callback the callback's type as determined by the BeanFactory.
* @param entityType the entity type to check.
* @return whether the given callback should be included in the candidates for the given callback type.
*/
static boolean supportsEvent(ResolvableType callbackType, ResolvableType entityType) {
return callbackType.as(EntityCallback.class).getGeneric(0).isAssignableFrom(entityType);
}
/**
* Determine whether the given callback supports the given entity type and callback type.
*
* @param callback the target callback to check.
* @param entityType the entity type to check.
* @param callbackType the source type to check against.
* @return whether the given callback should be included in the candidates for the given callback type.
*/
static boolean supportsEvent(EntityCallback<?> callback, ResolvableType entityType,
ResolvableType callbackType) {
ResolvableType callbackInstanceType = callback instanceof EntityCallbackAdapter<?> provider
? provider.getResolvableType()
: ResolvableType.forInstance(callback);
Assert.state(beanFactory != null,
"EntityCallbacks cannot retrieve callback beans because it is not associated with a BeanFactory");
return beanFactory;
return supportsEvent(callbackInstanceType, entityType)
&& callbackType.isAssignableFrom(callbackInstanceType);
}
/**
* Helper class that encapsulates a specific set of target {@link EntityCallback callbacks}, allowing for efficient
* retrieval of pre-filtered callbacks.
*/
class CallbackRetriever {
private static class CallbackRetriever {
private final Set<EntityCallback<?>> entityCallbacks = new LinkedHashSet<>();
private final Set<String> entityCallbackBeans = new LinkedHashSet<>();
private final boolean preFiltered;
CallbackRetriever(boolean preFiltered) {
this.preFiltered = preFiltered;
}
Collection<EntityCallback<?>> getEntityCallbacks() {
List<EntityCallback<?>> allCallbacks = new ArrayList<>(
this.entityCallbacks.size() + this.entityCallbackBeans.size());
allCallbacks.addAll(this.entityCallbacks);
if (!this.entityCallbackBeans.isEmpty()) {
BeanFactory beanFactory = getRequiredBeanFactory();
for (String callbackBeanName : this.entityCallbackBeans) {
try {
EntityCallback<?> callback = beanFactory.getBean(callbackBeanName, EntityCallback.class);
if (this.preFiltered || !allCallbacks.contains(callback)) {
allCallbacks.add(callback);
}
} catch (NoSuchBeanDefinitionException ex) {
// Singleton callback instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
if (!this.preFiltered || !this.entityCallbackBeans.isEmpty()) {
AnnotationAwareOrderComparator.sort(allCallbacks);
}
return allCallbacks;
return this.entityCallbacks;
}
void discoverEntityCallbacks(BeanFactory beanFactory) {
beanFactory.getBeanProvider(EntityCallback.class).stream().forEach(entityCallbacks::add);
}
}
/**
* Cache key for {@link EntityCallback}, based on event type and source type.
*/
static final class CallbackCacheKey implements Comparable<CallbackCacheKey> {
private final ResolvableType callbackType;
// We need both a ListableBeanFactory and BeanDefinitionRegistry here for advanced inspection.
// If we don't get that, use simple inspection.
if (!(beanFactory instanceof ListableBeanFactory && beanFactory instanceof BeanDefinitionRegistry)) {
beanFactory.getBeanProvider(EntityCallback.class).stream().forEach(entityCallbacks::add);
return;
}
private final Class<?> entityType;
var bf = (ListableBeanFactory & BeanDefinitionRegistry) beanFactory;
public CallbackCacheKey(ResolvableType callbackType, @Nullable Class<?> entityType) {
for (var beanName : bf.getBeanNamesForType(EntityCallback.class)) {
Assert.notNull(callbackType, "Callback type must not be null");
Assert.notNull(entityType, "Entity type must not be null");
EntityCallback<?> bean = EntityCallback.class.cast(bf.getBean(beanName));
this.callbackType = callbackType;
this.entityType = entityType;
}
ResolvableType type = ResolvableType.forClass(EntityCallback.class, bean.getClass());
ResolvableType entityType = type.getGeneric(0);
@Override
public boolean equals(Object other) {
if (entityType.resolve() != null) {
entityCallbacks.add(bean);
} else {
if (this == other) {
return true;
BeanDefinition definition = bf.getBeanDefinition(beanName);
entityCallbacks.add(new EntityCallbackAdapter<>(bean, definition.getResolvableType()));
}
}
CallbackCacheKey otherKey = (CallbackCacheKey) other;
return (this.callbackType.equals(otherKey.callbackType)
&& ObjectUtils.nullSafeEquals(this.entityType, otherKey.entityType));
}
}
/**
* A combination of an {@link EntityCallback}
*
* @author Oliver Drotbohm
*/
private static record EntityCallbackAdapter<T>(EntityCallback<T> delegate, ResolvableType type)
implements EntityCallback<T>, ResolvableTypeProvider {
@NonNull
@Override
public int hashCode() {
return this.callbackType.hashCode() * 17 + ObjectUtils.nullSafeHashCode(this.entityType);
public ResolvableType getResolvableType() {
return type;
}
}
/**
* Cache key for {@link EntityCallback}, based on event type and source type.
*/
private static record CallbackCacheKey(ResolvableType callbackType, @Nullable Class<?> entityType)
implements Comparable<CallbackCacheKey> {
@Override
public int compareTo(CallbackCacheKey other) {
return Comparators.<CallbackCacheKey> nullsHigh().thenComparing(it -> callbackType.toString())
return Comparators.<CallbackCacheKey> nullsHigh()
.thenComparing(it -> callbackType.toString())
.thenComparing(it -> entityType.getName()).compare(this, other);
}
}

7
src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java

@ -65,6 +65,7 @@ class DefaultEntityCallbacksUnitTests { @@ -65,6 +65,7 @@ class DefaultEntityCallbacksUnitTests {
var afterCallback = callbacks.callback(BeforeSaveCallback.class, personDocument);
assertThat(afterCallback).isSameAs(personDocument);
assertThat(afterCallback.getSsn()).isEqualTo(6);
}
@Test // DATACMNS-1467
@ -73,7 +74,8 @@ class DefaultEntityCallbacksUnitTests { @@ -73,7 +74,8 @@ class DefaultEntityCallbacksUnitTests {
var callbacks = new DefaultEntityCallbacks();
callbacks.addEntityCallback(new GenericPersonCallback());
Person afterCallback = callbacks.callback(GenericPersonCallback.class, new PersonDocument(null, "Walter", null));
Person afterCallback = callbacks.callback(GenericPersonCallback.class,
new PersonDocument(null, "Walter", null));
assertThat(afterCallback.getSsn()).isEqualTo(6);
}
@ -143,7 +145,8 @@ class DefaultEntityCallbacksUnitTests { @@ -143,7 +145,8 @@ class DefaultEntityCallbacksUnitTests {
var initial = new PersonDocument(null, "Walter", null);
assertThatIllegalArgumentException().isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
assertThatIllegalArgumentException()
.isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
assertThat(first.capturedValue()).isSameAs(initial);
assertThat(second.capturedValue()).isNotNull().isNotSameAs(initial);

34
src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java

@ -25,6 +25,7 @@ import java.util.concurrent.LinkedBlockingDeque; @@ -25,6 +25,7 @@ import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.AbstractBooleanAssert;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@ -43,6 +44,7 @@ import org.springframework.data.mapping.PersonDocument; @@ -43,6 +44,7 @@ import org.springframework.data.mapping.PersonDocument;
* @author Christoph Strobl
* @author Myeonghyeon Lee
* @author Mark Paluch
* @author Oliver Drotbohm
*/
class EntityCallbackDiscovererUnitTests {
@ -94,33 +96,23 @@ class EntityCallbackDiscovererUnitTests { @@ -94,33 +96,23 @@ class EntityCallbackDiscovererUnitTests {
assertThat(exceptions).isEmpty();
}
@Test // DATACMNS-1467
void shouldDiscoverCallbackTypeByName() {
var ctx = new AnnotationConfigApplicationContext(MyConfig.class);
var discoverer = new EntityCallbackDiscoverer(ctx);
discoverer.clear();
discoverer.addEntityCallbackBean("namedCallback");
Collection<EntityCallback<Person>> entityCallbacks = discoverer.getEntityCallbacks(PersonDocument.class,
ResolvableType.forType(BeforeSaveCallback.class));
assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyOtherCallback.class);
}
@Test // DATACMNS-1467
void shouldSupportCallbackTypes() {
var 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();
assertSupportsEvent(discoverer, MyBeforeSaveCallback.class, Person.class).isTrue();
assertSupportsEvent(discoverer, MyBeforeSaveCallback.class, Child.class).isTrue();
assertSupportsEvent(discoverer, BeforeSaveCallback.class, PersonDocument.class).isTrue();
assertSupportsEvent(discoverer, MyBeforeSaveCallback.class, Object.class).isFalse();
assertSupportsEvent(discoverer, MyBeforeSaveCallback.class, User.class).isFalse();
}
assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(Object.class))).isFalse();
assertThat(discoverer.supportsEvent(MyBeforeSaveCallback.class, ResolvableType.forClass(User.class))).isFalse();
private static AbstractBooleanAssert<?> assertSupportsEvent(EntityCallbackDiscoverer discoverer,
Class<?> callbackType, Class<?> entityType) {
return assertThat(
discoverer.supportsEvent(ResolvableType.forClass(callbackType), ResolvableType.forClass(entityType)));
}
@Test // DATACMNS-1467

Loading…
Cancel
Save