|
|
|
|
@@ -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<CallbackCacheKey, CallbackRetriever> retrieverCache = new ConcurrentHashMap<>(64);
|
|
|
|
|
private final Map<Class<?>, ResolvableType> entityTypeCache = new ConcurrentReferenceHashMap<>(64);
|
|
|
|
|
|
|
|
|
|
@@ -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,22 +84,16 @@ 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.retrieverCache.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void removeEntityCallback(EntityCallback<?> callback) {
|
|
|
|
|
|
|
|
|
|
synchronized (this.retrievalMutex) {
|
|
|
|
|
@@ -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 {
|
|
|
|
|
// 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 {
|
|
|
|
|
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<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 {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 {
|
|
|
|
|
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.
|
|
|
|
|
* <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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.state(beanFactory != null,
|
|
|
|
|
"EntityCallbacks cannot retrieve callback beans because it is not associated with a BeanFactory");
|
|
|
|
|
return beanFactory;
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bf = (ListableBeanFactory & BeanDefinitionRegistry) beanFactory;
|
|
|
|
|
|
|
|
|
|
for (var beanName : bf.getBeanNamesForType(EntityCallback.class)) {
|
|
|
|
|
|
|
|
|
|
EntityCallback<?> bean = EntityCallback.class.cast(bf.getBean(beanName));
|
|
|
|
|
|
|
|
|
|
ResolvableType type = ResolvableType.forClass(EntityCallback.class, bean.getClass());
|
|
|
|
|
ResolvableType entityType = type.getGeneric(0);
|
|
|
|
|
|
|
|
|
|
if (entityType.resolve() != null) {
|
|
|
|
|
entityCallbacks.add(bean);
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
BeanDefinition definition = bf.getBeanDefinition(beanName);
|
|
|
|
|
entityCallbacks.add(new EntityCallbackAdapter<>(bean, definition.getResolvableType()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 ResolvableType getResolvableType() {
|
|
|
|
|
return type;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cache key for {@link EntityCallback}, based on event type and source type.
|
|
|
|
|
*/
|
|
|
|
|
static final class CallbackCacheKey implements Comparable<CallbackCacheKey> {
|
|
|
|
|
|
|
|
|
|
private final ResolvableType callbackType;
|
|
|
|
|
|
|
|
|
|
private final Class<?> entityType;
|
|
|
|
|
|
|
|
|
|
public CallbackCacheKey(ResolvableType callbackType, @Nullable Class<?> entityType) {
|
|
|
|
|
|
|
|
|
|
Assert.notNull(callbackType, "Callback type must not be null");
|
|
|
|
|
Assert.notNull(entityType, "Entity type must not be null");
|
|
|
|
|
|
|
|
|
|
this.callbackType = callbackType;
|
|
|
|
|
this.entityType = entityType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean equals(Object other) {
|
|
|
|
|
|
|
|
|
|
if (this == other) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CallbackCacheKey otherKey = (CallbackCacheKey) other;
|
|
|
|
|
|
|
|
|
|
return (this.callbackType.equals(otherKey.callbackType)
|
|
|
|
|
&& ObjectUtils.nullSafeEquals(this.entityType, otherKey.entityType));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int hashCode() {
|
|
|
|
|
return this.callbackType.hashCode() * 17 + ObjectUtils.nullSafeHashCode(this.entityType);
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|