From a7b36ba2c0fcc3e37cb45c23e1d5c5fd5dd20c73 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 2 Jun 2023 08:33:36 +0200 Subject: [PATCH] Hacking - Explore Entity Metadata DSL --- .../config/MongoConfigurationSupport.java | 10 + .../core/convert/MappingMongoConverter.java | 17 +- .../mapping/BasicMongoPersistentEntity.java | 32 ++- .../mapping/BasicMongoPersistentProperty.java | 18 +- .../CachingMongoPersistentProperty.java | 5 +- .../mongodb/core/mapping/MappingConfig.java | 238 ++++++++++++++++++ .../core/mapping/MongoMappingContext.java | 15 +- .../core/mapping/MongoPersistentEntity.java | 5 + .../core/MongoTemplateMappingConfigTests.java | 97 +++++++ .../BasicMongoPersistentEntityUnitTests.java | 31 ++- 10 files changed, 457 insertions(+), 11 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MappingConfig.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingConfigTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java index aa49e49df..ddada3a53 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java @@ -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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 2bfb90150..d0a113249 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -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()) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index 9b87e0545..b090d3d74 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -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 extends BasicPersistentEntity typeInformation) { + this(typeInformation, null); + } /** * Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the @@ -79,12 +86,18 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity typeInformation) { + public BasicMongoPersistentEntity(TypeInformation typeInformation, EntityConfig 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 extends BasicPersistentEntity extends BasicPersistentEntity getEncryptionKeyIds() { @@ -398,9 +422,9 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity 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) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java index 79675ef33..50288de51 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java @@ -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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MappingConfig.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MappingConfig.java new file mode 100644 index 000000000..ff40bdcb0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MappingConfig.java @@ -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> entityConfigMap; + + MappingConfig(Map> entityConfigMap) { + this.entityConfigMap = entityConfigMap; + } + + public static MappingConfig none() { + return new MappingConfig(Collections.emptyMap()); + } + + public static MappingConfig mappingRules(Consumer customizer) { + MappingConfig mappingConfig = new MappingConfig(new HashMap<>()); + customizer.accept(new MappingRuleCustomizer() { + @Override + public MappingRuleCustomizer add(Class type, Consumer> cfg) { + + EntityConfig entityConfig = (EntityConfig) mappingConfig.entityConfigMap.computeIfAbsent(type, + (it) -> EntityConfig.configure(it)); + cfg.accept(entityConfig); + return this; + } + }); + return mappingConfig; + } + + public interface MappingRuleCustomizer { + MappingRuleCustomizer add(Class type, Consumer> cfg); + } + + @Nullable + public EntityConfig getEntityConfig(Class type) { + return (EntityConfig) entityConfigMap.get(type); + } + + public static class EntityConfig { + + private final Class type; + + @Nullable private Supplier collectionName; + Map> propertyConfigMap = new HashMap<>(); + EntityInstantiator instantiator; + + public EntityConfig(Class type) { + this.type = type; + } + + public static EntityConfig configure(Class type) { + return new EntityConfig<>(type); + } + + public

EntityConfig define(String name, Consumer> cfg) { + + PropertyConfig config = (PropertyConfig) propertyConfigMap.computeIfAbsent(name, + (key) -> new PropertyConfig<>(this.type, key)); + cfg.accept(config); + return this; + } + + public

EntityConfig define(Function property, Consumer> cfg) { + + String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath() + .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name")); + + return define(propertyName, cfg); + } + + public EntityConfig namespace(String name) { + return namespace(() -> name); + } + + public EntityConfig namespace(Supplier name) { + this.collectionName = name; + return this; + } + + boolean isIdProperty(PersistentProperty property) { + PropertyConfig propertyConfig = propertyConfigMap.get(property.getName()); + if (propertyConfig == null) { + return false; + } + + return propertyConfig.isId(); + } + + String collectionNameOrDefault(Supplier fallback) { + return collectionName != null ? collectionName.get() : fallback.get(); + } + + public EntityInstantiator getInstantiator() { + return instantiator; + } + + public EntityConfig entityCreator(Function, T> createFunction) { + + instantiator = new EntityInstantiator() { + + @Override + public , P extends PersistentProperty

> T createInstance( + E entity, ParameterValueProvider

provider) { + Map targetMap = new HashMap<>(); + + + PropertyValueProvider pvv = provider instanceof PropertyValueProvider pvp ? pvp : new PropertyValueProvider

() { + @Nullable + @Override + public 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 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 { + + V get(String arg); + + default V get(Function property) { + String propertyName = MethodInvocationRecorder.forProxyOf(getType()).record(property).getPropertyPath() + .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name")); + + return get(propertyName); + } + + Class getType(); + } + } + + public static class PropertyConfig { + + private final Class owingType; + private final String propertyName; + private String fieldName; + private boolean isId; + private boolean isTransient; + + public PropertyConfig(Class owingType, String propertyName) { + this.owingType = owingType; + this.propertyName = propertyName; + } + + public PropertyConfig useAsId() { + this.isId = true; + return this; + } + + public boolean isId() { + return isId; + } + + public PropertyConfig setTransient() { + this.isTransient = true; + return this; + } + + public PropertyConfig mappedName(String fieldName) { + this.fieldName = fieldName; + return this; + } + + public String getTargetName() { + return this.fieldName; + } + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java index 2877b1ba4..9dd745a9f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java @@ -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 customizer) { + setMappingConfig(MappingConfig.mappingRules(customizer)); + } + @Override protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { @@ -80,12 +91,12 @@ public class MongoMappingContext extends AbstractMappingContext 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 BasicMongoPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new BasicMongoPersistentEntity<>(typeInformation); + return new BasicMongoPersistentEntity<>(typeInformation, mappingConfig != null ? mappingConfig.getEntityConfig(typeInformation.getType()) : null); } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java index 1b6ab1cc5..ec1b505f4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java @@ -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 extends MutablePersistentEntity getEncryptionKeyIds(); + + default EntityInstantiator getInstanceCreator() { + return null; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingConfigTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingConfigTests.java new file mode 100644 index 000000000..c51627db9 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingConfigTests.java @@ -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 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; + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index 9804b3f9f..574744f83 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -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 entityConfig = configure(Sample.class) // + .namespace("my-collection") // + .define(Sample::getValue, PropertyConfig::useAsId) + .define(Sample::getName, property -> property.mappedName("n-a-m-e")); + + BasicMongoPersistentEntity 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 {}