diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
new file mode 100644
index 000000000..fdc276a6c
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright 2011-2020 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.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.Map.Entry;
+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.mapping.model.StaticPropertyAccessorFactory;
+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.StaticTypeInformation;
+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;
+
+/**
+ * Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and
+ * {@link PersistentProperty}.
+ *
+ * The implementation uses a {@link ReentrantReadWriteLock} to make sure {@link PersistentEntity} are completely
+ * populated before accessing them from outside.
+ *
+ * @param the concrete {@link PersistentEntity} type the {@link MappingContext} implementation creates
+ * @param
the concrete {@link PersistentProperty} type the {@link MappingContext} implementation creates
+ * @author Jon Brisbin
+ * @author Oliver Gierke
+ * @author Michael Hunger
+ * @author Thomas Darimont
+ * @author Tomasz Wysocki
+ * @author Mark Paluch
+ * @author Mikael Klamra
+ * @author Christoph Strobl
+ */
+public abstract class AbstractMappingContext, P extends PersistentProperty
>
+ implements MappingContext, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean {
+
+ private static final boolean IN_NATIVE_IMAGE = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
+
+ private final Optional NONE = Optional.empty();
+ private final Map, Optional> persistentEntities = new HashMap<>();
+ private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory;
+ private final PersistentPropertyPathFactory 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);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
+ */
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+ this.applicationEventPublisher = applicationEventPublisher;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
+ */
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+
+ this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
+
+ if (applicationEventPublisher == null) {
+ this.applicationEventPublisher = applicationContext;
+ }
+ }
+
+ /**
+ * Sets the {@link Set} of types to populate the context initially.
+ *
+ * @param initialEntitySet
+ */
+ public void setInitialEntitySet(Set extends Class>> initialEntitySet) {
+ this.initialEntitySet = initialEntitySet;
+ }
+
+ /**
+ * Configures whether the {@link MappingContext} is in strict mode which means, that it will throw
+ * {@link MappingException}s in case one tries to lookup a {@link PersistentEntity} not already in the context. This
+ * defaults to {@literal false} so that unknown types will be transparently added to the MappingContext if not known
+ * in advance.
+ *
+ * @param strict
+ */
+ public void setStrict(boolean strict) {
+ this.strict = strict;
+ }
+
+ /**
+ * Configures the {@link SimpleTypeHolder} to be used by the {@link MappingContext}. Allows customization of what
+ * types will be regarded as simple types and thus not recursively analyzed.
+ *
+ * @param simpleTypes must not be {@literal null}.
+ */
+ public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) {
+
+ Assert.notNull(simpleTypes, "SimpleTypeHolder must not be null!");
+
+ this.simpleTypeHolder = simpleTypes;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntities()
+ */
+ @Override
+ public Collection getPersistentEntities() {
+
+ try {
+
+ read.lock();
+
+ return persistentEntities.values().stream()//
+ .flatMap(Optionals::toStream)//
+ .collect(Collectors.toSet());
+
+ } finally {
+ read.unlock();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class)
+ */
+ @Nullable
+ public E getPersistentEntity(Class> type) {
+ return getPersistentEntity(ClassTypeInformation.from(type));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.MappingContext#hasPersistentEntityFor(java.lang.Class)
+ */
+ @Override
+ public boolean hasPersistentEntityFor(Class> type) {
+
+ Assert.notNull(type, "Type must not be null!");
+
+ Optional entity = persistentEntities.get(ClassTypeInformation.from(type));
+
+ return entity == null ? false : entity.isPresent();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(org.springframework.data.util.TypeInformation)
+ */
+ @Nullable
+ @Override
+ public E getPersistentEntity(TypeInformation> type) {
+
+ Assert.notNull(type, "Type must not be null!");
+
+ try {
+
+ read.lock();
+
+ Optional 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);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.MappingContext#getPersistentEntity(org.springframework.data.mapping.PersistentProperty)
+ */
+ @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());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(java.lang.Class, java.lang.String)
+ */
+ @Override
+ public PersistentPropertyPath
getPersistentPropertyPath(String propertyPath, Class> type) {
+ return persistentPropertyPathFactory.from(type, propertyPath);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.MappingContext#findPersistentPropertyPath(java.lang.Class, java.util.function.Predicate)
+ */
+ @Override
+ public PersistentPropertyPaths findPersistentPropertyPaths(Class 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());
+ }
+
+ /**
+ * Actually looks up the {@link PersistentPropertyPaths} for the given type, selection predicate and traversal guard.
+ * Primary purpose is to allow sub-types to alter the default traversal guard, e.g. used by
+ * {@link #findPersistentPropertyPaths(Class, Predicate)}.
+ *
+ * @param type will never be {@literal null}.
+ * @param predicate will never be {@literal null}.
+ * @param traversalGuard will never be {@literal null}.
+ * @return will never be {@literal null}.
+ * @see #findPersistentPropertyPaths(Class, Predicate)
+ */
+ protected final PersistentPropertyPaths doFindPersistentPropertyPaths(Class type,
+ Predicate super P> predicate, Predicate
traversalGuard) {
+ return persistentPropertyPathFactory.from(ClassTypeInformation.from(type), predicate, traversalGuard);
+ }
+
+ /**
+ * Adds the given type to the {@link MappingContext}.
+ *
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ protected Optional addPersistentEntity(Class> type) {
+ return addPersistentEntity(ClassTypeInformation.from(type));
+ }
+
+ /**
+ * Adds the given {@link TypeInformation} to the {@link MappingContext}.
+ *
+ * @param typeInformation must not be {@literal null}.
+ * @return
+ */
+ protected Optional addPersistentEntity(TypeInformation> typeInformation) {
+
+ Assert.notNull(typeInformation, "TypeInformation must not be null!");
+
+ try {
+
+ read.lock();
+
+ Optional 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);
+
+ // Eagerly cache the entity as we might have to find it during recursive lookups.
+ persistentEntities.put(typeInformation, Optional.of(entity));
+
+ if (typeInformation instanceof StaticTypeInformation>) {
+
+ // ((StaticTypeInformation>)typeInformation).doWithProperties()
+
+ Map> properties = ((StaticTypeInformation>) typeInformation).getProperties();
+ for (Entry> entry : properties.entrySet()) {
+
+ P target = createPersistentProperty(Property.of(typeInformation, entry.getKey()), entity, simpleTypeHolder);
+ entity.addPersistentProperty(target);
+
+ }
+ entity.setPersistentPropertyAccessorFactory(new StaticPropertyAccessorFactory());
+ return Optional.of(entity);
+ }
+
+ PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
+
+ final Map 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();
+ }
+
+ // Inform listeners
+ if (applicationEventPublisher != null && entity != null) {
+ applicationEventPublisher.publishEvent(new MappingContextEvent<>(this, entity));
+ }
+
+ return Optional.of(entity);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.PersistentEntityAware#getManagedTypes()
+ */
+ @Override
+ public Collection> getManagedTypes() {
+
+ try {
+
+ read.lock();
+ return Collections.unmodifiableSet(new HashSet<>(persistentEntities.keySet()));
+
+ } finally {
+ read.unlock();
+ }
+ }
+
+ /**
+ * Creates the concrete {@link PersistentEntity} instance.
+ *
+ * @param
+ * @param typeInformation
+ * @return
+ */
+ protected abstract E createPersistentEntity(TypeInformation typeInformation);
+
+ /**
+ * Creates the concrete instance of {@link PersistentProperty}.
+ *
+ * @param property
+ * @param owner
+ * @param simpleTypeHolder
+ * @return
+ */
+ protected abstract P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder);
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ @Override
+ public void afterPropertiesSet() {
+ initialize();
+ }
+
+ /**
+ * Initializes the mapping context. Will add the types configured through {@link #setInitialEntitySet(Set)} to the
+ * context.
+ */
+ public void initialize() {
+ initialEntitySet.forEach(this::addPersistentEntity);
+ }
+
+ /**
+ * Returns whether a {@link PersistentEntity} instance should be created for the given {@link TypeInformation}. By
+ * default this will reject all types considered simple and non-supported Kotlin classes, but it might be necessary to
+ * tweak that in case you have registered custom converters for top level types (which renders them to be considered
+ * simple) but still need meta-information about them.
+ *
+ *
+ * @param type will never be {@literal null}.
+ * @return
+ */
+ protected boolean shouldCreatePersistentEntityFor(TypeInformation> type) {
+
+ if (simpleTypeHolder.isSimpleType(type.getType())) {
+ return false;
+ }
+
+ return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
+ }
+
+ /**
+ * {@link FieldCallback} to create {@link PersistentProperty} instances.
+ *
+ * @author Oliver Gierke
+ */
+ private final class PersistentPropertyCreator implements FieldCallback {
+
+ private final E entity;
+ private final Map descriptors;
+ private final Map remainingDescriptors;
+
+ public PersistentPropertyCreator(E entity, Map descriptors) {
+ this(entity, descriptors, descriptors);
+ }
+
+ private PersistentPropertyCreator(E entity, Map descriptors,
+ Map remainingDescriptors) {
+ this.entity = entity;
+ this.descriptors = descriptors;
+ this.remainingDescriptors = remainingDescriptors;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.util.ReflectionUtils.FieldCallback#doWith(java.lang.reflect.Field)
+ */
+ 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);
+ }
+
+ /**
+ * Adds {@link PersistentProperty} instances for all suitable {@link PropertyDescriptor}s without a backing
+ * {@link Field}.
+ *
+ * @see PersistentPropertyFilter
+ */
+ 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);
+ }
+ }
+
+ /**
+ * Filter rejecting static fields as well as artificially introduced ones. See
+ * {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details.
+ *
+ * @author Oliver Gierke
+ */
+ static enum PersistentPropertyFilter implements FieldFilter {
+
+ INSTANCE;
+
+ private static final Streamable UNMAPPED_PROPERTIES;
+
+ static {
+
+ Set 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);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.util.ReflectionUtils.FieldFilter#matches(java.lang.reflect.Field)
+ */
+ public boolean matches(Field field) {
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ return false;
+ }
+
+ return !UNMAPPED_PROPERTIES.stream()//
+ .anyMatch(it -> it.matches(field.getName(), field.getType()));
+ }
+
+ /**
+ * Returns whether the given {@link PropertyDescriptor} is one to create a {@link PersistentProperty} for.
+ *
+ * @param property must not be {@literal null}.
+ * @return
+ */
+ 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()));
+ }
+
+ /**
+ * Value object to help defining property exclusion based on name patterns and types.
+ *
+ * @since 1.4
+ * @author Oliver Gierke
+ */
+ static class PropertyMatch {
+
+ private final @Nullable String namePattern;
+ private final @Nullable String typeName;
+
+ /**
+ * Creates a new {@link PropertyMatch} for the given name pattern and type name. At least one of the parameters
+ * must not be {@literal null}.
+ *
+ * @param namePattern a regex pattern to match field names, can be {@literal null}.
+ * @param typeName the name of the type to exclude, can be {@literal null}.
+ */
+ 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;
+ }
+
+ /**
+ * Returns whether the given {@link Field} matches the defined {@link PropertyMatch}.
+ *
+ * @param name must not be {@literal null}.
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ 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;
+ }
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java
new file mode 100644
index 000000000..5218e6c3a
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright 2011-2020 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.model;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.data.annotation.Immutable;
+import org.springframework.data.annotation.TypeAlias;
+import org.springframework.data.domain.Persistable;
+import org.springframework.data.mapping.*;
+import org.springframework.data.spel.EvaluationContextProvider;
+import org.springframework.data.support.IsNewStrategy;
+import org.springframework.data.support.PersistableIsNewStrategy;
+import org.springframework.data.util.Lazy;
+import org.springframework.data.util.StaticTypeInformation;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+/**
+ * Simple value object to capture information of {@link PersistentEntity}s.
+ *
+ * @author Oliver Gierke
+ * @author Jon Brisbin
+ * @author Patryk Wasik
+ * @author Thomas Darimont
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ */
+public class BasicPersistentEntity> implements MutablePersistentEntity {
+
+ private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!";
+
+ private final @Nullable PreferredConstructor constructor;
+ private final TypeInformation information;
+ private final List
properties;
+ private final List
persistentPropertiesCache;
+ private final @Nullable Comparator
comparator;
+ private final Set> associations;
+
+ private final Map propertyCache;
+ private final Map, Optional> annotationCache;
+ private final MultiValueMap, P> propertyAnnotationCache;
+
+ private @Nullable P idProperty;
+ private @Nullable P versionProperty;
+ private PersistentPropertyAccessorFactory propertyAccessorFactory;
+ private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
+
+ private final Lazy typeAlias;
+ private final Lazy isNewStrategy;
+ private final Lazy isImmutable;
+ private final Lazy requiresPropertyPopulation;
+
+ /**
+ * Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}.
+ *
+ * @param information must not be {@literal null}.
+ */
+ public BasicPersistentEntity(TypeInformation information) {
+ this(information, null);
+ }
+
+ /**
+ * Creates a new {@link BasicPersistentEntity} for the given {@link TypeInformation} and {@link Comparator}. The given
+ * {@link Comparator} will be used to define the order of the {@link PersistentProperty} instances added to the
+ * entity.
+ *
+ * @param information must not be {@literal null}.
+ * @param comparator can be {@literal null}.
+ */
+ public BasicPersistentEntity(TypeInformation information, @Nullable Comparator
comparator) {
+
+ Assert.notNull(information, "Information must not be null!");
+
+ this.information = information;
+ this.properties = new ArrayList<>();
+ this.persistentPropertiesCache = new ArrayList<>();
+ this.comparator = comparator;
+ this.constructor = information instanceof StaticTypeInformation ? null : PreferredConstructorDiscoverer.discover(this);
+ this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
+
+ this.propertyCache = new HashMap<>(16, 1f);
+ this.annotationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
+ this.propertyAnnotationCache = CollectionUtils
+ .toMultiValueMap(new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK));
+ this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE;
+ this.typeAlias = Lazy.of(() -> getAliasFromAnnotation(getType()));
+ this.isNewStrategy = Lazy.of(() -> Persistable.class.isAssignableFrom(information.getType()) //
+ ? PersistableIsNewStrategy.INSTANCE
+ : getFallbackIsNewStrategy());
+
+ this.isImmutable = Lazy.of(() -> isAnnotationPresent(Immutable.class));
+ this.requiresPropertyPopulation = Lazy.of(() -> !isImmutable() && properties.stream() //
+ .anyMatch(it -> !(isConstructorArgument(it) || it.isTransient())));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor()
+ */
+ @Nullable
+ public PreferredConstructor getPersistenceConstructor() {
+ return constructor;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty)
+ */
+ public boolean isConstructorArgument(PersistentProperty> property) {
+ return constructor != null && constructor.isConstructorParameter(property);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#isIdProperty(org.springframework.data.mapping.PersistentProperty)
+ */
+ public boolean isIdProperty(PersistentProperty> property) {
+ return idProperty != null && idProperty.equals(property);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#isVersionProperty(org.springframework.data.mapping.PersistentProperty)
+ */
+ public boolean isVersionProperty(PersistentProperty> property) {
+ return versionProperty != null && versionProperty.equals(property);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getName()
+ */
+ public String getName() {
+ return getType().getName();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getIdProperty()
+ */
+ @Nullable
+ public P getIdProperty() {
+ return idProperty;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getVersionProperty()
+ */
+ @Nullable
+ public P getVersionProperty() {
+ return versionProperty;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#hasIdProperty()
+ */
+ public boolean hasIdProperty() {
+ return idProperty != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#hasVersionProperty()
+ */
+ public boolean hasVersionProperty() {
+ return versionProperty != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MutablePersistentEntity#addPersistentProperty(P)
+ */
+ public void addPersistentProperty(P property) {
+
+ Assert.notNull(property, "Property must not be null!");
+
+ if (properties.contains(property)) {
+ return;
+ }
+
+ properties.add(property);
+
+ if (!property.isTransient() && !property.isAssociation()) {
+ persistentPropertiesCache.add(property);
+ }
+
+ propertyCache.computeIfAbsent(property.getName(), key -> property);
+
+ P candidate = returnPropertyIfBetterIdPropertyCandidateOrNull(property);
+
+ if (candidate != null) {
+ this.idProperty = candidate;
+ }
+
+ if (property.isVersionProperty()) {
+
+ P versionProperty = this.versionProperty;
+
+ if (versionProperty != null) {
+
+ throw new MappingException(
+ String.format(
+ "Attempt to add version property %s but already have property %s registered "
+ + "as version. Check your mapping configuration!",
+ property.getField(), versionProperty.getField()));
+ }
+
+ this.versionProperty = property;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MutablePersistentEntity#setEvaluationContextProvider(org.springframework.data.spel.EvaluationContextProvider)
+ */
+ @Override
+ public void setEvaluationContextProvider(EvaluationContextProvider provider) {
+ this.evaluationContextProvider = provider;
+ }
+
+ /**
+ * Returns the given property if it is a better candidate for the id property than the current id property.
+ *
+ * @param property the new id property candidate, will never be {@literal null}.
+ * @return the given id property or {@literal null} if the given property is not an id property.
+ */
+ @Nullable
+ protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) {
+
+ if (!property.isIdProperty()) {
+ return null;
+ }
+
+ P idProperty = this.idProperty;
+
+ if (idProperty != null) {
+ throw new MappingException(String.format("Attempt to add id property %s but already have property %s registered "
+ + "as id. Check your mapping configuration!", property.getField(), idProperty.getField()));
+ }
+
+ return property;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.MutablePersistentEntity#addAssociation(org.springframework.data.mapping.model.Association)
+ */
+ public void addAssociation(Association
association) {
+
+ Assert.notNull(association, "Association must not be null!");
+
+ associations.add(association);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String)
+ */
+ @Override
+ @Nullable
+ public P getPersistentProperty(String name) {
+ return propertyCache.get(name);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperties(java.lang.String)
+ */
+ @Override
+ public Iterable
getPersistentProperties(Class extends Annotation> annotationType) {
+
+ Assert.notNull(annotationType, "Annotation type must not be null!");
+ return propertyAnnotationCache.computeIfAbsent(annotationType, this::doFindPersistentProperty);
+ }
+
+ private List
doFindPersistentProperty(Class extends Annotation> annotationType) {
+
+ List