diff --git a/pom.xml b/pom.xml
index d00702b731..8eb8e02b68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index bb7d9f03cc..e477b31bfd 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index d3b7593d37..442f076df6 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
../pom.xml
@@ -50,7 +50,7 @@
org.springframework.data
spring-data-mongodb
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index b32dcba387..3ae97a7f89 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index b611cf01a8..44baa80cff 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-1849-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java
new file mode 100644
index 0000000000..815a68994d
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2019 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mongodb.core.convert.MongoConverter;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
+import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
+import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type;
+import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
+import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
+import org.springframework.data.mongodb.core.schema.MongoJsonSchema.MongoJsonSchemaBuilder;
+import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * {@link MongoJsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain
+ * domain type meta information which considers {@link org.springframework.data.mongodb.core.mapping.Field field names}
+ * and {@link org.springframework.data.mongodb.core.convert.MongoCustomConversions custom conversions}.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 2.2
+ */
+class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
+
+ private final MongoConverter converter;
+ private final MappingContext, MongoPersistentProperty> mappingContext;
+
+ /**
+ * Create a new instance of {@link MappingMongoJsonSchemaCreator}.
+ *
+ * @param converter must not be {@literal null}.
+ */
+ @SuppressWarnings("unchecked")
+ MappingMongoJsonSchemaCreator(MongoConverter converter) {
+
+ Assert.notNull(converter, "Converter must not be null!");
+ this.converter = converter;
+ this.mappingContext = (MappingContext, MongoPersistentProperty>) converter
+ .getMappingContext();
+ }
+
+ /*
+ * (non-Javadoc)
+ * org.springframework.data.mongodb.core.MongoJsonSchemaCreator#createSchemaFor(java.lang.Class)
+ */
+ @Override
+ public MongoJsonSchema createSchemaFor(Class> type) {
+
+ MongoPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(type);
+ MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder();
+
+ List schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity);
+ schemaBuilder.properties(schemaProperties.toArray(new JsonSchemaProperty[0]));
+
+ return schemaBuilder.build();
+
+ }
+
+ private List computePropertiesForEntity(List path,
+ MongoPersistentEntity> entity) {
+
+ List schemaProperties = new ArrayList<>();
+
+ for (MongoPersistentProperty nested : entity) {
+
+ List currentPath = new ArrayList<>(path);
+
+ if (path.contains(nested)) { // cycle guard
+ schemaProperties.add(createSchemaProperty(computePropertyFieldName(CollectionUtils.lastElement(currentPath)),
+ Object.class, false));
+ break;
+ }
+
+ currentPath.add(nested);
+ schemaProperties.add(computeSchemaForProperty(currentPath));
+ }
+
+ return schemaProperties;
+ }
+
+ private JsonSchemaProperty computeSchemaForProperty(List path) {
+
+ MongoPersistentProperty property = CollectionUtils.lastElement(path);
+
+ boolean required = isRequiredProperty(property);
+ Class> rawTargetType = computeTargetType(property); // target type before conversion
+ Class> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
+
+ if (property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) {
+ return createObjectSchemaPropertyForEntity(path, property, required);
+ }
+
+ String fieldName = computePropertyFieldName(property);
+
+ if (property.isCollectionLike()) {
+ return createSchemaProperty(fieldName, targetType, required);
+ } else if (property.isMap()) {
+ return createSchemaProperty(fieldName, Type.objectType(), required);
+ } else if (ClassUtils.isAssignable(Enum.class, targetType)) {
+ return createEnumSchemaProperty(fieldName, targetType, required);
+ }
+
+ return createSchemaProperty(fieldName, targetType, required);
+ }
+
+ private JsonSchemaProperty createObjectSchemaPropertyForEntity(List path,
+ MongoPersistentProperty property, boolean required) {
+
+ ObjectJsonSchemaProperty target = JsonSchemaProperty.object(property.getName());
+ List nestedProperties = computePropertiesForEntity(path,
+ mappingContext.getRequiredPersistentEntity(property));
+
+ return createPotentiallyRequiredSchemaProperty(
+ target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required);
+ }
+
+ private JsonSchemaProperty createEnumSchemaProperty(String fieldName, Class> targetType, boolean required) {
+
+ List
+ *
+ * @author Christoph Strobl
+ * @since 2.2
+ */
+public interface MongoJsonSchemaCreator {
+
+ /**
+ * Create the {@link MongoJsonSchema} for the given {@link Class type}.
+ *
+ * @param type must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ MongoJsonSchema createSchemaFor(Class> type);
+
+ /**
+ * Creates a new {@link MongoJsonSchemaCreator} that is aware of conversions applied by the given
+ * {@link MongoConverter}.
+ *
+ * @param mongoConverter must not be {@literal null}.
+ * @return new instance of {@link MongoJsonSchemaCreator}.
+ */
+ static MongoJsonSchemaCreator create(MongoConverter mongoConverter) {
+
+ Assert.notNull(mongoConverter, "MongoConverter must not be null!");
+ return new MappingMongoJsonSchemaCreator(mongoConverter);
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java
index a5071ad33f..b166815615 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java
@@ -15,13 +15,16 @@
*/
package org.springframework.data.mongodb.core.convert;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.UnaryOperator;
import org.bson.Document;
import org.bson.conversions.Bson;
+
+import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.convert.TypeAliasAccessor;
@@ -58,21 +61,58 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper implements M
private final TypeAliasAccessor accessor;
private final @Nullable String typeKey;
+ private UnaryOperator> writeTarget = UnaryOperator.identity();
+ /**
+ * Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code _class}.
+ */
public DefaultMongoTypeMapper() {
this(DEFAULT_TYPE_KEY);
}
+ /**
+ * Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}.
+ *
+ * @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
+ */
public DefaultMongoTypeMapper(@Nullable String typeKey) {
- this(typeKey, Arrays.asList(new SimpleTypeInformationMapper()));
+ this(typeKey, Collections.singletonList(new SimpleTypeInformationMapper()));
}
+ /**
+ * Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}.
+ *
+ * @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
+ * @param mappingContext the mapping context.
+ */
public DefaultMongoTypeMapper(@Nullable String typeKey,
MappingContext extends PersistentEntity, ?>, ?> mappingContext) {
this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext,
- Arrays.asList(new SimpleTypeInformationMapper()));
+ Collections.singletonList(new SimpleTypeInformationMapper()));
}
+ /**
+ * Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}. Uses
+ * {@link UnaryOperator} to apply {@link CustomConversions}.
+ *
+ * @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
+ * @param mappingContext the mapping context to look up types using type hints.
+ * @see MappingMongoConverter#getWriteTarget(Class)
+ */
+ public DefaultMongoTypeMapper(@Nullable String typeKey,
+ MappingContext extends PersistentEntity, ?>, ?> mappingContext, UnaryOperator> writeTarget) {
+ this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext,
+ Collections.singletonList(new SimpleTypeInformationMapper()));
+ this.writeTarget = writeTarget;
+ }
+
+ /**
+ * Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}. Uses
+ * {@link TypeInformationMapper} to map type hints.
+ *
+ * @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
+ * @param mappers
+ */
public DefaultMongoTypeMapper(@Nullable String typeKey, List extends TypeInformationMapper> mappers) {
this(typeKey, new DocumentTypeAliasAccessor(typeKey), null, mappers);
}
@@ -120,6 +160,15 @@ public void writeTypeRestrictions(Document result, @Nullable Set> restr
accessor.writeTypeTo(result, new Document("$in", restrictedMappedTypes));
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.convert.MongoTypeMapper#getWriteTargetTypeFor(java.lang.Class)
+ */
+ @Override
+ public Class> getWriteTargetTypeFor(Class> source) {
+ return writeTarget.apply(source);
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.convert.DefaultTypeMapper#getFallbackTypeFor(java.lang.Object)
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 d62baf31e9..ad821e69a3 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
@@ -15,23 +15,33 @@
*/
package org.springframework.data.mongodb.core.convert;
-import java.util.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiator;
-import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
@@ -112,7 +122,8 @@ public MappingMongoConverter(DbRefResolver dbRefResolver,
this.dbRefResolver = dbRefResolver;
this.mappingContext = mappingContext;
- this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext);
+ this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext,
+ this::getWriteTarget);
this.idMapper = new QueryMapper(this);
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
@@ -660,6 +671,10 @@ private static Collection> asCollection(Object source) {
protected List