Compare commits

...

2 Commits

Author SHA1 Message Date
Christoph Strobl
a7b36ba2c0 Hacking - Explore Entity Metadata DSL 2023-06-06 15:01:46 +02:00
Christoph Strobl
54dc3dd7f8 Prepare issue branch 2023-06-01 13:59:09 +02:00
14 changed files with 461 additions and 15 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.x-3380-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.x-3380-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.x-3380-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.x-3380-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.bson.UuidRepresentation;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -34,7 +35,10 @@ import org.springframework.data.mongodb.MongoManagedTypes;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MappingConfig;
import org.springframework.data.mongodb.core.mapping.MappingConfig.MappingRuleCustomizer;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -87,10 +91,16 @@ public abstract class MongoConfigurationSupport {
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
mappingContext.setAutoIndexCreation(autoIndexCreation());
mappingContext.setMappingConfig(mappingConfig());
return mappingContext;
}
@Nullable
public MappingConfig mappingConfig() {
return null;
}
/**
* @return new instance of {@link MongoManagedTypes}.
* @throws ClassNotFoundException

View File

@@ -487,7 +487,22 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
&& instanceCreatorMetadata.hasParameters() ? getParameterProvider(context, entity, documentAccessor, evaluator)
: NoOpParameterValueProvider.INSTANCE;
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
EntityInstantiator instantiator = entity.getInstanceCreator();
if(instantiator != null) {
provider = new ParameterValueProvider() {
@Nullable
public Object getParameterValue(Parameter parameter) {
String name = parameter.getName();
if (name == null) {
throw new IllegalArgumentException(String.format("Parameter %s does not have a name", parameter));
} else {
return documentAccessor.get(entity.getRequiredPersistentProperty(name));
}
}
};
} else {
instantiator = instantiators.getInstantiatorFor(entity);
}
S instance = instantiator.createInstance(entity, provider);
if (entity.requiresPropertyPopulation()) {

View File

@@ -31,7 +31,9 @@ import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mongodb.MongoCollectionUtils;
import org.springframework.data.mongodb.core.mapping.MappingConfig.EntityConfig;
import org.springframework.data.mongodb.util.encryption.EncryptionUtils;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.Lazy;
@@ -72,6 +74,11 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
private final @Nullable Expression collationExpression;
private final ShardKey shardKey;
private EntityConfig entityConfig;
public BasicMongoPersistentEntity(TypeInformation<T> typeInformation) {
this(typeInformation, null);
}
/**
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
@@ -79,12 +86,18 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
*
* @param typeInformation must not be {@literal null}.
*/
public BasicMongoPersistentEntity(TypeInformation<T> typeInformation) {
public BasicMongoPersistentEntity(TypeInformation<T> typeInformation, EntityConfig<T> config) {
super(typeInformation, MongoPersistentPropertyComparator.INSTANCE);
this.entityConfig = config;
Class<?> rawType = typeInformation.getType();
String fallback = MongoCollectionUtils.getPreferredCollectionName(rawType);
if (config != null) {
fallback = config.collectionNameOrDefault(() -> MongoCollectionUtils.getPreferredCollectionName(rawType));
}
if (this.isAnnotationPresent(Document.class)) {
Document document = this.getRequiredAnnotation(Document.class);
@@ -249,6 +262,12 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
Assert.notNull(property, "MongoPersistentProperty must not be null");
if (entityConfig != null) {
if (entityConfig.isIdProperty(property)) {
return property;
}
}
if (!property.isIdProperty()) {
return null;
}
@@ -340,6 +359,11 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
}
}
@Override
public EntityInstantiator getInstanceCreator() {
return this.entityConfig != null ? this.entityConfig.getInstantiator() : null;
}
@Override
public Collection<Object> getEncryptionKeyIds() {
@@ -398,9 +422,9 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
if (persistentProperty.isDbReference() && persistentProperty.getDBRef().lazy()) {
if (persistentProperty.isArray() || Modifier.isFinal(persistentProperty.getActualType().getModifiers())) {
throw new MappingException(String.format(
"Invalid lazy DBRef property for %s; Found %s which must not be an array nor a final class",
persistentProperty.getField(), persistentProperty.getActualType()));
throw new MappingException(
String.format("Invalid lazy DBRef property for %s; Found %s which must not be an array nor a final class",
persistentProperty.getField(), persistentProperty.getActualType()));
}
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mongodb.core.mapping.MappingConfig.PropertyConfig;
import org.springframework.data.mongodb.util.encryption.EncryptionUtils;
import org.springframework.data.util.Lazy;
import org.springframework.expression.EvaluationContext;
@@ -73,6 +74,12 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
}
private final FieldNamingStrategy fieldNamingStrategy;
PropertyConfig<?, ?> propertyConfig;
public BasicMongoPersistentProperty(Property property, MongoPersistentEntity<?> owner,
SimpleTypeHolder simpleTypeHolder, @Nullable FieldNamingStrategy fieldNamingStrategy) {
this(property, owner, simpleTypeHolder, fieldNamingStrategy, null);
}
/**
* Creates a new {@link BasicMongoPersistentProperty}.
@@ -83,11 +90,12 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
* @param fieldNamingStrategy can be {@literal null}.
*/
public BasicMongoPersistentProperty(Property property, MongoPersistentEntity<?> owner,
SimpleTypeHolder simpleTypeHolder, @Nullable FieldNamingStrategy fieldNamingStrategy) {
SimpleTypeHolder simpleTypeHolder, @Nullable FieldNamingStrategy fieldNamingStrategy, @Nullable PropertyConfig<?,?> propertyConfig) {
super(property, owner, simpleTypeHolder);
this.fieldNamingStrategy = fieldNamingStrategy == null ? PropertyNameFieldNamingStrategy.INSTANCE
: fieldNamingStrategy;
this.propertyConfig = propertyConfig;
if (isIdProperty() && hasExplicitFieldName()) {
@@ -115,6 +123,10 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
return true;
}
if(propertyConfig != null && propertyConfig.isId()) {
return true;
}
// We need to support a wider range of ID types than just the ones that can be converted to an ObjectId
// but still we need to check if there happens to be an explicit name set
return SUPPORTED_ID_PROPERTY_NAMES.contains(getName()) && !hasExplicitFieldName();
@@ -132,6 +144,10 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
*/
public String getFieldName() {
if(propertyConfig != null && StringUtils.hasText(propertyConfig.getTargetName())) {
return propertyConfig.getTargetName();
}
if (isIdProperty()) {
if (getOwner().getIdProperty() == null) {

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.mapping;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mongodb.core.mapping.MappingConfig.PropertyConfig;
import org.springframework.lang.Nullable;
/**
@@ -47,8 +48,8 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
* @param fieldNamingStrategy can be {@literal null}.
*/
public CachingMongoPersistentProperty(Property property, MongoPersistentEntity<?> owner,
SimpleTypeHolder simpleTypeHolder, @Nullable FieldNamingStrategy fieldNamingStrategy) {
super(property, owner, simpleTypeHolder, fieldNamingStrategy);
SimpleTypeHolder simpleTypeHolder, @Nullable FieldNamingStrategy fieldNamingStrategy, PropertyConfig config) {
super(property, owner, simpleTypeHolder, fieldNamingStrategy, config);
}
@Override

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2023 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.mongodb.core.mapping;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.MethodInvocationRecorder;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 2023/06
*/
public class MappingConfig {
private final Map<Class, EntityConfig<?>> entityConfigMap;
MappingConfig(Map<Class, EntityConfig<?>> entityConfigMap) {
this.entityConfigMap = entityConfigMap;
}
public static MappingConfig none() {
return new MappingConfig(Collections.emptyMap());
}
public static MappingConfig mappingRules(Consumer<MappingRuleCustomizer> customizer) {
MappingConfig mappingConfig = new MappingConfig(new HashMap<>());
customizer.accept(new MappingRuleCustomizer() {
@Override
public <T> MappingRuleCustomizer add(Class<T> type, Consumer<EntityConfig<T>> cfg) {
EntityConfig<T> entityConfig = (EntityConfig<T>) mappingConfig.entityConfigMap.computeIfAbsent(type,
(it) -> EntityConfig.configure(it));
cfg.accept(entityConfig);
return this;
}
});
return mappingConfig;
}
public interface MappingRuleCustomizer {
<T> MappingRuleCustomizer add(Class<T> type, Consumer<EntityConfig<T>> cfg);
}
@Nullable
public <T> EntityConfig<T> getEntityConfig(Class<T> type) {
return (EntityConfig<T>) entityConfigMap.get(type);
}
public static class EntityConfig<T> {
private final Class<T> type;
@Nullable private Supplier<String> collectionName;
Map<String, PropertyConfig<T, ?>> propertyConfigMap = new HashMap<>();
EntityInstantiator instantiator;
public EntityConfig(Class<T> type) {
this.type = type;
}
public static <T, P> EntityConfig<T> configure(Class<T> type) {
return new EntityConfig<>(type);
}
public <P> EntityConfig<T> define(String name, Consumer<PropertyConfig<T, P>> cfg) {
PropertyConfig<T, P> config = (PropertyConfig<T, P>) propertyConfigMap.computeIfAbsent(name,
(key) -> new PropertyConfig<>(this.type, key));
cfg.accept(config);
return this;
}
public <P> EntityConfig<T> define(Function<T, P> property, Consumer<PropertyConfig<T, P>> cfg) {
String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name"));
return define(propertyName, cfg);
}
public EntityConfig<T> namespace(String name) {
return namespace(() -> name);
}
public EntityConfig<T> namespace(Supplier<String> name) {
this.collectionName = name;
return this;
}
boolean isIdProperty(PersistentProperty<?> property) {
PropertyConfig<T, ?> propertyConfig = propertyConfigMap.get(property.getName());
if (propertyConfig == null) {
return false;
}
return propertyConfig.isId();
}
String collectionNameOrDefault(Supplier<String> fallback) {
return collectionName != null ? collectionName.get() : fallback.get();
}
public EntityInstantiator getInstantiator() {
return instantiator;
}
public EntityConfig<T> entityCreator(Function<Arguments<T>, T> createFunction) {
instantiator = new EntityInstantiator() {
@Override
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(
E entity, ParameterValueProvider<P> provider) {
Map<String, Object> targetMap = new HashMap<>();
PropertyValueProvider pvv = provider instanceof PropertyValueProvider pvp ? pvp : new PropertyValueProvider<P>() {
@Nullable
@Override
public <T> T getPropertyValue(P property) {
Parameter parameter = new Parameter<>(property.getName(), (TypeInformation) property.getTypeInformation(),
new Annotation[] {}, null);
return (T) provider.getParameterValue(parameter);
}
};
entity.doWithProperties((SimplePropertyHandler) property -> {
targetMap.put(property.getName(), pvv.getPropertyValue(property));
});
return (T) createFunction.apply(new Arguments() {
private Map<Function, String> resolvedName = new HashMap<>();
@Override
public Object get(String arg) {
return targetMap.get(arg);
}
@Override
public Class getType() {
return entity.getType();
}
@Override
public Object get(Function property) {
String name = resolvedName.computeIfAbsent(property, key -> (String) MethodInvocationRecorder.forProxyOf(getType()).record(property).getPropertyPath().orElse(""));
return get(name);
}
});
}
};
return this;
}
public interface Arguments<T> {
<V> V get(String arg);
default <V> V get(Function<T, V> property) {
String propertyName = MethodInvocationRecorder.forProxyOf(getType()).record(property).getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name"));
return get(propertyName);
}
Class<T> getType();
}
}
public static class PropertyConfig<T, P> {
private final Class<T> owingType;
private final String propertyName;
private String fieldName;
private boolean isId;
private boolean isTransient;
public PropertyConfig(Class<T> owingType, String propertyName) {
this.owingType = owingType;
this.propertyName = propertyName;
}
public PropertyConfig<T, P> useAsId() {
this.isId = true;
return this;
}
public boolean isId() {
return isId;
}
public PropertyConfig<T, P> setTransient() {
this.isTransient = true;
return this;
}
public PropertyConfig<T, P> mappedName(String fieldName) {
this.fieldName = fieldName;
return this;
}
public String getTargetName() {
return this.fieldName;
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.mapping;
import java.util.AbstractMap;
import java.util.function.Consumer;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@@ -26,6 +27,7 @@ import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mongodb.core.mapping.MappingConfig.MappingRuleCustomizer;
import org.springframework.data.util.NullableWrapperConverters;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -45,6 +47,7 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY;
private boolean autoIndexCreation = false;
private MappingConfig mappingConfig;
@Nullable
private ApplicationContext applicationContext;
@@ -67,6 +70,14 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
this.fieldNamingStrategy = fieldNamingStrategy == null ? DEFAULT_NAMING_STRATEGY : fieldNamingStrategy;
}
public void setMappingConfig(MappingConfig mappingConfig) {
this.mappingConfig = mappingConfig;
}
public void mappingRules(Consumer<MappingRuleCustomizer> customizer) {
setMappingConfig(MappingConfig.mappingRules(customizer));
}
@Override
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
@@ -80,12 +91,12 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
@Override
public MongoPersistentProperty createPersistentProperty(Property property, MongoPersistentEntity<?> owner,
SimpleTypeHolder simpleTypeHolder) {
return new CachingMongoPersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy);
return new CachingMongoPersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy, mappingConfig != null ? mappingConfig.getEntityConfig(owner.getType()).propertyConfigMap.get(property.getName()) : null);
}
@Override
protected <T> BasicMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
return new BasicMongoPersistentEntity<>(typeInformation);
return new BasicMongoPersistentEntity<>(typeInformation, mappingConfig != null ? mappingConfig.getEntityConfig(typeInformation.getType()) : null);
}
@Override

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.mapping;
import java.util.Collection;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.MutablePersistentEntity;
import org.springframework.lang.Nullable;
@@ -111,4 +112,8 @@ public interface MongoPersistentEntity<T> extends MutablePersistentEntity<T, Mon
*/
@Nullable
Collection<Object> getEncryptionKeyIds();
default EntityInstantiator getInstanceCreator() {
return null;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2023 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.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.mapping.MappingConfig.*;
import lombok.Data;
import java.util.List;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.MongoClients;
/**
* @author Christoph Strobl
* @since 2023/06
*/
public class MongoTemplateMappingConfigTests {
@Test
void testProgrammaticMetadata() {
SimpleMongoClientDatabaseFactory dbFactory = new SimpleMongoClientDatabaseFactory(MongoClients.create(),
"test-manual-config");
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.mappingRules(rules -> {
rules.add(Sample.class, cfg -> {
cfg.namespace("my-sample");
cfg.entityCreator(args -> {
return new Sample(args.get(Sample::getName));
});
cfg.define(Sample::getName, PropertyConfig::useAsId);
cfg.define(Sample::getValue, property -> property.mappedName("va-l-ue"));
});
});
mappingContext.afterPropertiesSet();
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbFactory, mappingContext);
mappingMongoConverter.afterPropertiesSet();
MongoTemplate template = new MongoTemplate(dbFactory, mappingMongoConverter);
template.dropCollection(Sample.class);
Sample sample = new Sample("s1");
sample.value = "val";
template.save(sample);
Document dbValue = template.execute("my-sample", collection -> {
return collection.find(new Document()).first();
});
System.out.println("dbValue: " + dbValue);
assertThat(dbValue).containsEntry("_id", sample.name).containsEntry("va-l-ue", sample.value);
List<Sample> entries = template.find(Query.query(Criteria.where("name").is(sample.name)), Sample.class);
entries.forEach(System.out::println);
assertThat(entries).containsExactly(sample);
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = "my-sample")
static class Sample {
Sample(String name) {
this.name = name;
}
@Id final String name;
@Field(name = "va-l-ue") String value;
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.mapping;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.mapping.MappingConfig.EntityConfig.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -26,18 +27,21 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.mapping.MappingConfig.EntityConfig;
import org.springframework.data.mongodb.core.mapping.MappingConfig.PropertyConfig;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* Unit tests for {@link BasicMongoPersistentEntity}.
@@ -301,6 +305,31 @@ public class BasicMongoPersistentEntityUnitTests {
return new BasicMongoPersistentEntity<>(ClassTypeInformation.from(type));
}
@Data
class Sample {
String name;
String value;
}
@Test
void testProgrammaticMetadata() {
doReturn("value").when(propertyMock).getName();
EntityConfig<Sample> entityConfig = configure(Sample.class) //
.namespace("my-collection") //
.define(Sample::getValue, PropertyConfig::useAsId)
.define(Sample::getName, property -> property.mappedName("n-a-m-e"));
BasicMongoPersistentEntity<Sample> entity = new BasicMongoPersistentEntity<>(TypeInformation.of(Sample.class), entityConfig);
entity.addPersistentProperty(propertyMock);
MongoPersistentProperty idProperty = entity.getIdProperty();
assertThat(idProperty).isSameAs(propertyMock);
assertThat(entity.getCollection()).isEqualTo("my-collection");
}
@Document("contacts")
class Contact {}