diff --git a/src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java b/src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java index 6bb3fc852..b844aa474 100644 --- a/src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java +++ b/src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java @@ -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; * * @author Mark Paluch * @author Christoph Strobl + * @author Oliver Drotbohm * @since 2.2 */ 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 invokeCallback(EntityCallback callback, T entity, @@ -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 diff --git a/src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java b/src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java index f10665e30..abd59f592 100644 --- a/src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java +++ b/src/main/java/org/springframework/data/mapping/callback/DefaultReactiveEntityCallbacks.java @@ -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; * @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 { } @Override + @SuppressWarnings("unchecked") public Mono callback(Class callbackType, T entity, Object... args) { Assert.notNull(entity, "Entity must not be null"); - Class entityType = (Class) (entity != null ? ClassUtils.getUserClass(entity.getClass()) + Class entityType = (Class) (entity != null + ? ClassUtils.getUserClass(entity.getClass()) : callbackDiscoverer.resolveDeclaredEntityType(callbackType).getRawClass()); Method callbackMethod = callbackMethodCache.computeIfAbsent(callbackType, it -> { @@ -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(); diff --git a/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java b/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java index 7a699a673..f2a4780f5 100644 --- a/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java @@ -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; * @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 retrieverCache = new ConcurrentHashMap<>(64); private final Map, ResolvableType> entityTypeCache = new ConcurrentReferenceHashMap<>(64); @@ -64,9 +68,8 @@ class EntityCallbackDiscoverer { EntityCallbackDiscoverer() {} /** - * Create a new {@link EntityCallback} instance. - *

- * Pre loads {@link EntityCallback} beans by scanning the {@link BeanFactory}. + * Create a new {@link EntityCallback} instance.

>) (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>) (Collection) retriever.getEntityCallbacks(); + return (Collection) retriever.getEntityCallbacks(); } - retriever = new CallbackRetriever(true); + retriever = new CallbackRetriever(); Collection> callbacks = retrieveEntityCallbacks(ResolvableType.forClass(sourceType), callbackType, retriever); this.retrieverCache.put(cacheKey, retriever); - return (Collection>) (Collection) callbacks; + return (Collection) callbacks; } } else { // No CallbackRetriever caching -> no synchronization necessary - return (Collection>) (Collection) retrieveEntityCallbacks(callbackType, callbackType, null); + return (Collection) retrieveEntityCallbacks(callbackType, callbackType, null); } } @@ -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 { * @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> retrieveEntityCallbacks(ResolvableType entityType, ResolvableType callbackType, - @Nullable CallbackRetriever retriever) { + private Collection> retrieveEntityCallbacks(ResolvableType entityType, + ResolvableType callbackType, @Nullable CallbackRetriever retriever) { List> allCallbacks = new ArrayList<>(); Set> callbacks; - Set 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 { } /** - * Filter a callback early through checking its generically declared entity type before trying to instantiate it. - *

- * 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 { 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 { 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 { } 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 { 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 { }; } - private BeanFactory getRequiredBeanFactory() { + /** + * Filter a callback early through checking its generically declared entity type before trying to instantiate it. + *

+ * 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> entityCallbacks = new LinkedHashSet<>(); - private final Set entityCallbackBeans = new LinkedHashSet<>(); - - private final boolean preFiltered; - - CallbackRetriever(boolean preFiltered) { - this.preFiltered = preFiltered; - } - Collection> getEntityCallbacks() { - - List> 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 { - - 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(EntityCallback delegate, ResolvableType type) + implements EntityCallback, 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 { @Override public int compareTo(CallbackCacheKey other) { - return Comparators. nullsHigh().thenComparing(it -> callbackType.toString()) + return Comparators. nullsHigh() + .thenComparing(it -> callbackType.toString()) .thenComparing(it -> entityType.getName()).compare(this, other); - } } diff --git a/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java index 48eac92d6..2c4dcc025 100644 --- a/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java @@ -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 { 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 { 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); diff --git a/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java index 358c59bf1..7f3110cd7 100644 --- a/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java @@ -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; * @author Christoph Strobl * @author Myeonghyeon Lee * @author Mark Paluch + * @author Oliver Drotbohm */ 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> 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