package org.springframework.data.mapping.context;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory;
import org.springframework.data.mapping.model.MutablePersistentEntity;
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean {
private static final boolean IN_NATIVE_IMAGE = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
private final Optional<E> NONE = Optional.empty();
private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory;
private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
private @Nullable ApplicationEventPublisher applicationEventPublisher;
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
private boolean strict = false;
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock read = lock.readLock();
private final Lock write = lock.writeLock();
protected AbstractMappingContext() {
this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this);
EntityInstantiators instantiators = new EntityInstantiators();
PersistentPropertyAccessorFactory accessorFactory = IN_NATIVE_IMAGE ? BeanWrapperPropertyAccessorFactory.INSTANCE
: new ClassGeneratingPropertyAccessorFactory();
this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
instantiators);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
if (applicationEventPublisher == null) {
this.applicationEventPublisher = applicationContext;
}
}
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
this.initialEntitySet = initialEntitySet;
}
public void setStrict(boolean strict) {
this.strict = strict;
}
public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) {
Assert.notNull(simpleTypes, "SimpleTypeHolder must not be null!");
this.simpleTypeHolder = simpleTypes;
}
@Override
public Collection<E> getPersistentEntities() {
try {
read.lock();
return persistentEntities.values().stream()
.flatMap(Optionals::toStream)
.collect(Collectors.toSet());
} finally {
read.unlock();
}
}
@Nullable
public E getPersistentEntity(Class<?> type) {
return getPersistentEntity(ClassTypeInformation.from(type));
}
@Override
public boolean hasPersistentEntityFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
Optional<E> entity = persistentEntities.get(ClassTypeInformation.from(type));
return entity == null ? false : entity.isPresent();
}
@Nullable
@Override
public E getPersistentEntity(TypeInformation<?> type) {
Assert.notNull(type, "Type must not be null!");
try {
read.lock();
Optional<E> entity = persistentEntities.get(type);
if (entity != null) {
return entity.orElse(null);
}
} finally {
read.unlock();
}
if (!shouldCreatePersistentEntityFor(type)) {
try {
write.lock();
persistentEntities.put(type, NONE);
} finally {
write.unlock();
}
return null;
}
if (strict) {
throw new MappingException("Unknown persistent entity " + type);
}
return addPersistentEntity(type).orElse(null);
}
@Nullable
@Override
public E getPersistentEntity(P persistentProperty) {
Assert.notNull(persistentProperty, "PersistentProperty must not be null!");
if (!persistentProperty.isEntity()) {
return null;
}
TypeInformation<?> typeInfo = persistentProperty.getTypeInformation();
return getPersistentEntity(typeInfo.getRequiredActualType());
}
@Override
public PersistentPropertyPath<P> getPersistentPropertyPath(PropertyPath propertyPath) {
return persistentPropertyPathFactory.from(propertyPath);
}
@Override
public PersistentPropertyPath<P> getPersistentPropertyPath(String propertyPath, Class<?> type) {
return persistentPropertyPathFactory.from(type, propertyPath);
}
@Override
public <T> PersistentPropertyPaths<T, P> findPersistentPropertyPaths(Class<T> type, Predicate<? super P> predicate) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(predicate, "Selection predicate must not be null!");
return doFindPersistentPropertyPaths(type, predicate, it -> !it.isAssociation());
}
protected final <T> PersistentPropertyPaths<T, P> doFindPersistentPropertyPaths(Class<T> type,
Predicate<? super P> predicate, Predicate<P> traversalGuard) {
return persistentPropertyPathFactory.from(ClassTypeInformation.from(type), predicate, traversalGuard);
}
protected Optional<E> addPersistentEntity(Class<?> type) {
return addPersistentEntity(ClassTypeInformation.from(type));
}
protected Optional<E> addPersistentEntity(TypeInformation<?> typeInformation) {
Assert.notNull(typeInformation, "TypeInformation must not be null!");
try {
read.lock();
Optional<E> persistentEntity = persistentEntities.get(typeInformation);
if (persistentEntity != null) {
return persistentEntity;
}
} finally {
read.unlock();
}
Class<?> type = typeInformation.getType();
E entity = null;
try {
write.lock();
entity = createPersistentEntity(typeInformation);
entity.setEvaluationContextProvider(evaluationContextProvider);
persistentEntities.put(typeInformation, Optional.of(entity));
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
final Map<String, PropertyDescriptor> descriptors = new HashMap<>();
for (PropertyDescriptor descriptor : pds) {
descriptors.put(descriptor.getName(), descriptor);
}
try {
PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
persistentPropertyCreator.addPropertiesForRemainingDescriptors();
entity.verify();
if (persistentPropertyAccessorFactory.isSupported(entity)) {
entity.setPersistentPropertyAccessorFactory(persistentPropertyAccessorFactory);
}
} catch (RuntimeException e) {
persistentEntities.remove(typeInformation);
throw e;
}
} catch (BeansException e) {
throw new MappingException(e.getMessage(), e);
} finally {
write.unlock();
}
if (applicationEventPublisher != null && entity != null) {
applicationEventPublisher.publishEvent(new MappingContextEvent<>(this, entity));
}
return Optional.of(entity);
}
@Override
public Collection<TypeInformation<?>> getManagedTypes() {
try {
read.lock();
return Collections.unmodifiableSet(new HashSet<>(persistentEntities.keySet()));
} finally {
read.unlock();
}
}
protected abstract <T> E createPersistentEntity(TypeInformation<T> typeInformation);
protected abstract P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder);
@Override
public void afterPropertiesSet() {
initialize();
}
public void initialize() {
initialEntitySet.forEach(this::addPersistentEntity);
}
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
if (simpleTypeHolder.isSimpleType(type.getType())) {
return false;
}
return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
}
private final class PersistentPropertyCreator implements FieldCallback {
private final E entity;
private final Map<String, PropertyDescriptor> descriptors;
private final Map<String, PropertyDescriptor> remainingDescriptors;
public PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descriptors) {
this(entity, descriptors, descriptors);
}
private PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descriptors,
Map<String, PropertyDescriptor> remainingDescriptors) {
this.entity = entity;
this.descriptors = descriptors;
this.remainingDescriptors = remainingDescriptors;
}
public void doWith(Field field) {
String fieldName = field.getName();
TypeInformation<?> type = entity.getTypeInformation();
ReflectionUtils.makeAccessible(field);
Property property = Optional.ofNullable(descriptors.get(fieldName))
.map(it -> Property.of(type, field, it))
.orElseGet(() -> Property.of(type, field));
createAndRegisterProperty(property);
this.remainingDescriptors.remove(fieldName);
}
public void addPropertiesForRemainingDescriptors() {
remainingDescriptors.values().stream()
.filter(Property::supportsStandalone)
.map(it -> Property.of(entity.getTypeInformation(), it))
.filter(PersistentPropertyFilter.INSTANCE::matches)
.forEach(this::createAndRegisterProperty);
}
private void createAndRegisterProperty(Property input) {
P property = createPersistentProperty(input, entity, simpleTypeHolder);
if (property.isTransient()) {
return;
}
if (!input.isFieldBacked() && !property.usePropertyAccess()) {
return;
}
entity.addPersistentProperty(property);
if (property.isAssociation()) {
entity.addAssociation(property.getRequiredAssociation());
}
if (entity.getType().equals(property.getRawType())) {
return;
}
property.getPersistentEntityTypes().forEach(AbstractMappingContext.this::addPersistentEntity);
}
}
static enum PersistentPropertyFilter implements FieldFilter {
INSTANCE;
private static final Streamable<PropertyMatch> UNMAPPED_PROPERTIES;
static {
Set<PropertyMatch> matches = new HashSet<>();
matches.add(new PropertyMatch("class", null));
matches.add(new PropertyMatch("this\\$.*", null));
matches.add(new PropertyMatch("metaClass", "groovy.lang.MetaClass"));
UNMAPPED_PROPERTIES = Streamable.of(matches);
}
public boolean matches(Field field) {
if (Modifier.isStatic(field.getModifiers())) {
return false;
}
return !UNMAPPED_PROPERTIES.stream()
.anyMatch(it -> it.matches(field.getName(), field.getType()));
}
public boolean matches(Property property) {
Assert.notNull(property, "Property must not be null!");
if (!property.hasAccessor()) {
return false;
}
return !UNMAPPED_PROPERTIES.stream()
.anyMatch(it -> it.matches(property.getName(), property.getType()));
}
static class PropertyMatch {
private final @Nullable String namePattern;
private final @Nullable String typeName;
public PropertyMatch(@Nullable String namePattern, @Nullable String typeName) {
Assert.isTrue(!(namePattern == null && typeName == null), "Either name pattern or type name must be given!");
this.namePattern = namePattern;
this.typeName = typeName;
}
public boolean matches(String name, Class<?> type) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(type, "Type must not be null!");
if (namePattern != null && !name.matches(namePattern)) {
return false;
}
if (typeName != null && !type.getName().equals(typeName)) {
return false;
}
return true;
}
}
}
}