DATAMONGO-1849 - Polishing.
Fix generics usage in MappingMongoJsonSchemaCreator. Make fields final. Rename MappingMongoConverter.computeWriteTarget to getWriteTarget and expose it publicly for reuse in custom DefaultTypeMapper setups without the need to subclass MappingMongoConverter. Remove Nullability functionality for required fields as nullability indicators should originate from PersistentProperty and PreferredConstructor. Update documentation. Related ticket: DATACMNS-1513 Original pull request: #733.
This commit is contained in:
@@ -21,12 +21,11 @@ import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
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;
|
||||
@@ -35,7 +34,6 @@ 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.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -45,26 +43,28 @@ 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 MongoConverter converter;
|
||||
private MappingContext mappingContext;
|
||||
private final MongoConverter converter;
|
||||
private final MappingContext<MongoPersistentEntity<?>, 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 = converter.getMappingContext();
|
||||
|
||||
this.mappingContext = (MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>) converter
|
||||
.getMappingContext();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -74,7 +74,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
@Override
|
||||
public MongoJsonSchema createSchemaFor(Class<?> type) {
|
||||
|
||||
PersistentEntity<?, ?> entity = mappingContext.getPersistentEntity(type);
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
|
||||
MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder();
|
||||
|
||||
List<JsonSchemaProperty> schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity);
|
||||
@@ -84,35 +84,33 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
|
||||
}
|
||||
|
||||
private List<JsonSchemaProperty> computePropertiesForEntity(List<PersistentProperty> path,
|
||||
PersistentEntity<?, ?> entity) {
|
||||
private List<JsonSchemaProperty> computePropertiesForEntity(List<MongoPersistentProperty> path,
|
||||
MongoPersistentEntity<?> entity) {
|
||||
|
||||
List<JsonSchemaProperty> schemaProperties = new ArrayList<>();
|
||||
entity.doWithProperties((SimplePropertyHandler) nested -> {
|
||||
|
||||
ArrayList<PersistentProperty> currentPath = new ArrayList<>(path);
|
||||
for (MongoPersistentProperty nested : entity) {
|
||||
|
||||
List<MongoPersistentProperty> currentPath = new ArrayList<>(path);
|
||||
|
||||
if (path.contains(nested)) { // cycle guard
|
||||
schemaProperties.add(createSchemaProperty(computePropertyFieldName(CollectionUtils.lastElement(currentPath)),
|
||||
Object.class, false));
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
currentPath.add(nested);
|
||||
JsonSchemaProperty jsonSchemaProperty = computeSchemaForProperty(currentPath, entity);
|
||||
if (jsonSchemaProperty != null) {
|
||||
schemaProperties.add(jsonSchemaProperty);
|
||||
}
|
||||
});
|
||||
schemaProperties.add(computeSchemaForProperty(currentPath));
|
||||
}
|
||||
|
||||
return schemaProperties;
|
||||
}
|
||||
|
||||
private JsonSchemaProperty computeSchemaForProperty(List<PersistentProperty> path, PersistentEntity<?, ?> parent) {
|
||||
private JsonSchemaProperty computeSchemaForProperty(List<MongoPersistentProperty> path) {
|
||||
|
||||
PersistentProperty property = CollectionUtils.lastElement(path);
|
||||
MongoPersistentProperty property = CollectionUtils.lastElement(path);
|
||||
|
||||
boolean required = isRequiredProperty(parent, property);
|
||||
boolean required = isRequiredProperty(property);
|
||||
Class<?> rawTargetType = computeTargetType(property); // target type before conversion
|
||||
Class<?> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
|
||||
|
||||
@@ -133,12 +131,12 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
return createSchemaProperty(fieldName, targetType, required);
|
||||
}
|
||||
|
||||
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<PersistentProperty> path,
|
||||
PersistentProperty property, boolean required) {
|
||||
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,
|
||||
MongoPersistentProperty property, boolean required) {
|
||||
|
||||
ObjectJsonSchemaProperty target = JsonSchemaProperty.object(property.getName());
|
||||
List<JsonSchemaProperty> nestedProperties = computePropertiesForEntity(path,
|
||||
mappingContext.getPersistentEntity(property));
|
||||
mappingContext.getRequiredPersistentEntity(property));
|
||||
|
||||
return createPotentiallyRequiredSchemaProperty(
|
||||
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required);
|
||||
@@ -147,6 +145,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
private JsonSchemaProperty createEnumSchemaProperty(String fieldName, Class<?> targetType, boolean required) {
|
||||
|
||||
List<Object> possibleValues = new ArrayList<>();
|
||||
|
||||
for (Object enumValue : EnumSet.allOf((Class) targetType)) {
|
||||
possibleValues.add(converter.convertToMongoType(enumValue));
|
||||
}
|
||||
@@ -178,10 +177,8 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
: property.getName();
|
||||
}
|
||||
|
||||
private boolean isRequiredProperty(PersistentEntity<?, ?> parent, PersistentProperty property) {
|
||||
|
||||
return (parent.isConstructorArgument(property) && !property.isAnnotationPresent(Nullable.class))
|
||||
|| property.getType().isPrimitive();
|
||||
private boolean isRequiredProperty(PersistentProperty property) {
|
||||
return property.getType().isPrimitive();
|
||||
}
|
||||
|
||||
private Class<?> computeTargetType(PersistentProperty<?> property) {
|
||||
@@ -196,7 +193,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
}
|
||||
|
||||
if (mongoProperty.hasExplicitWriteTarget()) {
|
||||
return mongoProperty.findAnnotation(Field.class).targetType().getJavaClass();
|
||||
return mongoProperty.getRequiredAnnotation(Field.class).targetType().getJavaClass();
|
||||
}
|
||||
|
||||
return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType();
|
||||
|
||||
@@ -23,29 +23,30 @@ import org.springframework.util.Assert;
|
||||
* {@link MongoJsonSchemaCreator} extracts the {@link MongoJsonSchema} for a given {@link Class} by applying the
|
||||
* following mapping rules.
|
||||
* <p>
|
||||
* <strong>Required Properties</strong><br />
|
||||
* - All Constructor arguments annotated with {@link org.springframework.lang.Nullable}. <br />
|
||||
* - Properties of primitive type. <br />
|
||||
* </p>
|
||||
* <p>
|
||||
* <strong>Ignored Properties</strong><br />
|
||||
* - All properties annotated with {@link org.springframework.data.annotation.Transient}. <br />
|
||||
* </p>
|
||||
* <p>
|
||||
* <strong>Property Type Mapping</strong><br />
|
||||
* - {@link java.lang.Object} -> {@code type : 'object'} <br />
|
||||
* - {@link java.util.Arrays} -> {@code type : 'array'} <br />
|
||||
* - {@link java.util.Collection} -> {@code type : 'array'} <br />
|
||||
* - {@link java.util.Map} -> {@code type : 'object'} <br />
|
||||
* - {@link java.lang.Enum} -> {@code type : 'string', enum : [the enum values]} <br />
|
||||
* - Simple Types -> {@code type : 'the corresponding bson type' } <br />
|
||||
* - Domain Types -> {@code type : 'object', properties : {the types properties} } <br />
|
||||
* <strong>Required Properties</strong>
|
||||
* <ul>
|
||||
* <li>Properties of primitive type</li>
|
||||
* </ul>
|
||||
* <strong>Ignored Properties</strong>
|
||||
* <ul>
|
||||
* <li>All properties annotated with {@link org.springframework.data.annotation.Transient}</li>
|
||||
* </ul>
|
||||
* <strong>Property Type Mapping</strong>
|
||||
* <ul>
|
||||
* <li>{@link java.lang.Object} -> {@code type : 'object'}</li>
|
||||
* <li>{@link java.util.Arrays} -> {@code type : 'array'}</li>
|
||||
* <li>{@link java.util.Collection} -> {@code type : 'array'}</li>
|
||||
* <li>{@link java.util.Map} -> {@code type : 'object'}</li>
|
||||
* <li>{@link java.lang.Enum} -> {@code type : 'string', enum : [the enum values]}</li>
|
||||
* <li>Simple Types -> {@code type : 'the corresponding bson type' }</li>
|
||||
* <li>Domain Types -> {@code type : 'object', properties : {the types properties} }</li>
|
||||
* </ul>
|
||||
* <br />
|
||||
* {@link org.springframework.data.annotation.Id _id} properties using types that can be converted into
|
||||
* {@link org.bson.types.ObjectId} like {@link String} will be mapped to {@code type : 'object'} unless there is more
|
||||
* specific information available via the {@link org.springframework.data.mongodb.core.mapping.MongoId} annotation.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
*/
|
||||
@@ -66,7 +67,7 @@ public interface MongoJsonSchemaCreator {
|
||||
* @param mongoConverter must not be {@literal null}.
|
||||
* @return new instance of {@link MongoJsonSchemaCreator}.
|
||||
*/
|
||||
static MongoJsonSchemaCreator jsonSchemaCreator(MongoConverter mongoConverter) {
|
||||
static MongoJsonSchemaCreator create(MongoConverter mongoConverter) {
|
||||
|
||||
Assert.notNull(mongoConverter, "MongoConverter must not be null!");
|
||||
return new MappingMongoJsonSchemaCreator(mongoConverter);
|
||||
|
||||
@@ -15,14 +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.Function;
|
||||
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;
|
||||
@@ -59,29 +61,58 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper<Bson> implements M
|
||||
|
||||
private final TypeAliasAccessor<Bson> accessor;
|
||||
private final @Nullable String typeKey;
|
||||
private Function<Class<?>, Class<?>> writeTarget = it -> it;
|
||||
private UnaryOperator<Class<?>> 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, Function<Class<?>, Class<?>> writeTarget) {
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext, UnaryOperator<Class<?>> writeTarget) {
|
||||
this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext,
|
||||
Arrays.asList(new SimpleTypeInformationMapper()));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
@@ -122,7 +123,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
this.dbRefResolver = dbRefResolver;
|
||||
this.mappingContext = mappingContext;
|
||||
this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext,
|
||||
this::computeWriteTarget);
|
||||
this::getWriteTarget);
|
||||
this.idMapper = new QueryMapper(this);
|
||||
|
||||
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
|
||||
@@ -672,7 +673,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
if (!property.isDbReference()) {
|
||||
|
||||
if (property.hasExplicitWriteTarget()) {
|
||||
return writeCollectionInternal(collection, new HijackedTypeInformation<>(property), new ArrayList<>());
|
||||
return writeCollectionInternal(collection, new TypeInformationWrapper<>(property), new ArrayList<>());
|
||||
}
|
||||
return writeCollectionInternal(collection, property.getTypeInformation(), new BasicDBList());
|
||||
}
|
||||
@@ -1608,6 +1609,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param ref
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Document readRef(DBRef ref) {
|
||||
return dbRefResolver.fetch(ref);
|
||||
}
|
||||
@@ -1630,7 +1632,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @return
|
||||
* @since 2.2
|
||||
*/
|
||||
protected Class<?> computeWriteTarget(Class<?> source) {
|
||||
public Class<?> getWriteTarget(Class<?> source) {
|
||||
return conversions.getCustomWriteTarget(source).orElse(source);
|
||||
}
|
||||
|
||||
@@ -1702,12 +1704,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private static class HijackedTypeInformation<S> implements TypeInformation<S> {
|
||||
private static class TypeInformationWrapper<S> implements TypeInformation<S> {
|
||||
|
||||
private MongoPersistentProperty persistentProperty;
|
||||
private TypeInformation<?> delegate;
|
||||
|
||||
public HijackedTypeInformation(MongoPersistentProperty property) {
|
||||
public TypeInformationWrapper(MongoPersistentProperty property) {
|
||||
|
||||
this.persistentProperty = property;
|
||||
this.delegate = property.getTypeInformation();
|
||||
|
||||
@@ -31,8 +31,9 @@ import org.bson.types.ObjectId;
|
||||
* <p/>
|
||||
* Bson types are identified by a {@code byte} {@link #getBsonType() value}. This enumeration typically returns the
|
||||
* according bson type value except for {@link #IMPLICIT} which is a marker to derive the field type from a property.
|
||||
*
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
* @see org.bson.BsonType
|
||||
*/
|
||||
@@ -67,7 +68,7 @@ public enum FieldType {
|
||||
|
||||
/**
|
||||
* Returns the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
|
||||
*
|
||||
*
|
||||
* @return the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
|
||||
*/
|
||||
public int getBsonType() {
|
||||
@@ -76,7 +77,7 @@ public enum FieldType {
|
||||
|
||||
/**
|
||||
* Returns the Java class used to represent the type.
|
||||
*
|
||||
*
|
||||
* @return the Java class used to represent the type.
|
||||
*/
|
||||
public Class<?> getJavaClass() {
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
@@ -34,10 +36,12 @@ import org.springframework.data.mongodb.core.mapping.FieldType;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoId;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingMongoJsonSchemaCreator}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
|
||||
@@ -58,52 +62,25 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(VariousFieldTypes.class);
|
||||
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class))
|
||||
.isEqualTo(Document.parse(VARIOUS_FIELD_TYPES));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void requiredCtorArgs() {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(RequiredArgsCtor.class);
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class))
|
||||
.isEqualTo(Document.parse(REQUIRED_ARGS_CTOR));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void withNestedObject() {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(WithNestedDomainType.class);
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class))
|
||||
.isEqualTo(Document.parse(WITH_NESTED_DOMAIN_TYPE));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void withNestedThatHasRequiredFieldsObject() {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(WithNestedDomainTypeHavingRequiredCtor.class);
|
||||
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class))
|
||||
.isEqualTo(Document.parse(WITH_NESTED_DOMAIN_TYPE_HAVING_REQUIRED_CTOR));
|
||||
assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(Document.parse(VARIOUS_FIELD_TYPES));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void withRemappedIdType() {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(WithExplicitMongoIdTypeMapping.class);
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class))
|
||||
.isEqualTo(Document.parse(WITH_EXPLICIT_MONGO_ID_TYPE_MAPPING));
|
||||
assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(WITH_EXPLICIT_MONGO_ID_TYPE_MAPPING);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void cyclic() {
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(Cyclic.class);
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(Document.parse(CYCLIC));
|
||||
assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(CYCLIC);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1849
|
||||
public void convterRegisterd() {
|
||||
public void converterRegistered() {
|
||||
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
|
||||
MongoCustomConversions mcc = new MongoCustomConversions(
|
||||
@@ -114,8 +91,8 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
schemaCreator = new MappingMongoJsonSchemaCreator(converter);
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.createSchemaFor(WithNestedDomainType.class);
|
||||
Assertions.assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(Document.parse(
|
||||
"{ 'type' : 'object', 'properties' : { '_id' : { 'type' : 'object' }, 'nested' : { 'type' : 'object' } } }"));
|
||||
assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo(
|
||||
"{ 'type' : 'object', 'properties' : { '_id' : { 'type' : 'object' }, 'nested' : { 'type' : 'object' } } }");
|
||||
}
|
||||
|
||||
// --> TYPES AND JSON
|
||||
@@ -171,32 +148,6 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
JustSomeEnum enumProperty;
|
||||
}
|
||||
|
||||
// --> REQUIRED ARGS (CTOR)
|
||||
|
||||
static final String REQUIRED_ARGS_CTOR = "" + //
|
||||
"{" + //
|
||||
" 'type' : 'object'," + //
|
||||
" 'required' : ['requiredCtorArg']," + //
|
||||
" 'properties' : {" + //
|
||||
" 'requiredCtorArg' : { 'type' : 'string' }," + //
|
||||
" 'nullableCtorArg' : { 'type' : 'string' }," + //
|
||||
" 'optionalArg' : { 'type' : 'string' }" + //
|
||||
" }" + //
|
||||
"}";
|
||||
|
||||
static class RequiredArgsCtor {
|
||||
|
||||
String requiredCtorArg;
|
||||
@Nullable String nullableCtorArg;
|
||||
String optionalArg;
|
||||
|
||||
public RequiredArgsCtor(String requiredCtorArg, @Nullable String nullableCtorArg) {
|
||||
|
||||
this.requiredCtorArg = requiredCtorArg;
|
||||
this.nullableCtorArg = nullableCtorArg;
|
||||
}
|
||||
}
|
||||
|
||||
// --> NESTED DOMAIN TYPE
|
||||
|
||||
static final String WITH_NESTED_DOMAIN_TYPE = "" + //
|
||||
@@ -214,21 +165,6 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
VariousFieldTypes nested;
|
||||
}
|
||||
|
||||
static final String WITH_NESTED_DOMAIN_TYPE_HAVING_REQUIRED_CTOR = "" + //
|
||||
"{" + //
|
||||
" 'type' : 'object'," + //
|
||||
" 'properties' : {" + //
|
||||
" '_id' : { 'type' : 'object' }," + //
|
||||
" 'nested' : " + REQUIRED_ARGS_CTOR + //
|
||||
" }" + //
|
||||
"}";
|
||||
|
||||
static class WithNestedDomainTypeHavingRequiredCtor {
|
||||
|
||||
String id;
|
||||
RequiredArgsCtor nested;
|
||||
}
|
||||
|
||||
// --> EXPLICIT MONGO_ID MAPPING
|
||||
|
||||
final String WITH_EXPLICIT_MONGO_ID_TYPE_MAPPING = "" + //
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* Changed behavior of `Reactive/MongoOperations#count` now limiting the range to count matches within by passing on _offset_ & _limit_ to the server.
|
||||
* Kotlin extension methods accepting `KClass` are deprecated now in favor of `reified` methods.
|
||||
* Support of array filters in `Update` operations.
|
||||
* <<mongo.jsonSchema.generated, Json Schema generation>> from domain types.
|
||||
* <<mongo.jsonSchema.generated, JSON Schema generation>> from domain types.
|
||||
|
||||
[[new-features.2-1-0]]
|
||||
== What's New in Spring Data MongoDB 2.1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
== Custom Conversions - Overriding Default Mapping
|
||||
|
||||
The most trivial way of influencing the the mapping result is by specifying the desired native MongoDB target type via the
|
||||
`@Field` annotation. This allows to work with non mongoDB types like `BigDecimal` in the domain model while persisting
|
||||
`@Field` annotation. This allows to work with non MongoDB types like `BigDecimal` in the domain model while persisting
|
||||
values in native `org.bson.types.Decimal128` format.
|
||||
|
||||
.Explicit target type mapping
|
||||
|
||||
@@ -87,15 +87,14 @@ template.createCollection(Person.class, CollectionOptions.empty().schema(schema)
|
||||
====
|
||||
|
||||
[[mongo.jsonSchema.generated]]
|
||||
==== Generating the Schema
|
||||
==== Generating a Schema
|
||||
|
||||
Setting up a schema can be a time consuming task and we encourage everyone who decides to do so, to really take the time
|
||||
it takes. It's important, schema changes can be hard. However there might be times when one does not want to balked
|
||||
with it, and that is where the `JsonSchemaCreator` comes into play.
|
||||
Setting up a schema can be a time consuming task and we encourage everyone who decides to do so, to really take the time it takes.
|
||||
It's important, schema changes can be hard.
|
||||
However, there might be times when one does not want to balked with it, and that is where `JsonSchemaCreator` comes into play.
|
||||
|
||||
The `JsonSchemaCreator` and its default implementation generate the `MongoJsonSchema` out of the domain types metadata provided
|
||||
by the mapping infrastructure. This means that <<mapping-usage-annotations, annotated properties>> as well as potential <<mapping-configuration, custom conversions>>
|
||||
are considered.
|
||||
`JsonSchemaCreator` and its default implementation generates a `MongoJsonSchema` out of domain types metadata provided by the mapping infrastructure.
|
||||
This means, that <<mapping-usage-annotations, annotated properties>> as well as potential <<mapping-configuration, custom conversions>> are considered.
|
||||
|
||||
.Generate Json Schema from domain type
|
||||
====
|
||||
@@ -103,25 +102,24 @@ are considered.
|
||||
----
|
||||
public class Person {
|
||||
|
||||
private final String firstname; <1>
|
||||
private final @Nullable String lastname; <2>
|
||||
private int age; <3>
|
||||
private Species species; <4>
|
||||
private Address address; <5>
|
||||
private @Field(fieldType=SCRIPT) String theForce; <6>
|
||||
private @Transient Boolean useTheForce; <7>
|
||||
private final String firstname; <1>
|
||||
private final int age; <2>
|
||||
private Species species; <3>
|
||||
private Address address; <4>
|
||||
private @Field(fieldType=SCRIPT) String theForce; <5>
|
||||
private @Transient Boolean useTheForce; <6>
|
||||
|
||||
public Person(String firstname, @Nullable String lastname) { <1> <2>
|
||||
public Person(String firstname, int age) { <1> <2>
|
||||
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
// gettter / setter omitted
|
||||
}
|
||||
|
||||
MongoJsonSchema schema = schemaCreator.jsonSchemaCreator(mongoOperations.getConverter())
|
||||
.createSchemaFor(DomainType.class);
|
||||
MongoJsonSchema schema = MongoJsonSchemaCreator.create(mongoOperations.getConverter())
|
||||
.createSchemaFor(Person.class);
|
||||
|
||||
template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
|
||||
----
|
||||
@@ -130,38 +128,36 @@ template.createCollection(Person.class, CollectionOptions.empty().schema(schema)
|
||||
----
|
||||
{
|
||||
'type' : 'object',
|
||||
'required' : ['firstname', 'age'], <1> <3>
|
||||
'required' : ['age'], <2>
|
||||
'properties' : {
|
||||
'firstname' : { 'type' : 'string' }, <1>
|
||||
'lastname' : { 'type' : 'string' }, <2>
|
||||
'age' : { 'bsonType' : 'int' } <3>
|
||||
'species' : { <4>
|
||||
'firstname' : { 'type' : 'string' }, <1>
|
||||
'age' : { 'bsonType' : 'int' } <2>
|
||||
'species' : { <3>
|
||||
'type' : 'string',
|
||||
'enum' : ['HUMAN', 'WOOKIE', 'UNKNOWN']
|
||||
}
|
||||
'address' : { <5>
|
||||
'address' : { <4>
|
||||
'type' : 'object'
|
||||
'properties' : {
|
||||
'postCode' : { 'type': 'string' }
|
||||
}
|
||||
},
|
||||
'theForce' : { 'type' : 'javascript'} <6>
|
||||
'theForce' : { 'type' : 'javascript'} <5>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Required property as **not** `@Nullable` and used in the constructor.
|
||||
<2> Optional property though used in the constructor it is still `@Nullable`.
|
||||
<3> Primitive types are considered required properties.
|
||||
<4> Enums are restricted to possible values.
|
||||
<5> Object type properties get are inspected themselfes.
|
||||
<6> `String` type property that is truned into `Code` by the mapping.
|
||||
<7> `@Transient` properties are left out when generating the schema.
|
||||
<1> Simple object properties are consideres regular properties.
|
||||
<2> Primitive types are considered required properties
|
||||
<3> Enums are restricted to possible values.
|
||||
<4> Object type properties are inspected and represented as nested documents.
|
||||
<5> `String` type property that is converted to `Code` by the converter.
|
||||
<6> `@Transient` properties are omitted when generating the schema.
|
||||
====
|
||||
|
||||
NOTE: `_id` properties using types that can be converted into `ObjectId` like `String` are mapped to `{ type : 'object' }`
|
||||
unless there is more specific information available via the `@MongoId` annotation.
|
||||
|
||||
[cols="3,1,6", options="header"]
|
||||
[cols="2,2,6", options="header"]
|
||||
.Sepcial Schema Generation rules
|
||||
|===
|
||||
| Java
|
||||
@@ -169,33 +165,33 @@ unless there is more specific information available via the `@MongoId` annotatio
|
||||
| Notes
|
||||
|
||||
| `Object`
|
||||
| type : object
|
||||
| `type : object`
|
||||
| with `properties` if metadata available.
|
||||
|
||||
| `Collection`
|
||||
| type : array
|
||||
| `type : array`
|
||||
| -
|
||||
|
||||
| `Map`
|
||||
| type : object
|
||||
| `type : object`
|
||||
| -
|
||||
|
||||
| `Enum`
|
||||
| type : string
|
||||
| `type : string`
|
||||
| with `enum` property holding the possible enumeration values.
|
||||
|
||||
| `array`
|
||||
| type : array
|
||||
| `type : array`
|
||||
| simple type array unless it's a `byte[]`
|
||||
|
||||
| `byte[]`
|
||||
| bsonType : binData
|
||||
| `bsonType : binData`
|
||||
| -
|
||||
|
||||
|===
|
||||
|
||||
[[mongo.jsonSchema.query]]
|
||||
==== Query a collection for matching Json Schema
|
||||
==== Query a collection for matching JSON Schema
|
||||
|
||||
You can use a schema to query any collection for documents that match a given structure defined by a JSON schema, as the following example shows:
|
||||
|
||||
@@ -210,7 +206,7 @@ template.find(query(matchingDocumentStructure(schema)), Person.class);
|
||||
====
|
||||
|
||||
[[mongo.jsonSchema.types]]
|
||||
==== Json Schema Types
|
||||
==== JSON Schema Types
|
||||
|
||||
The following table shows the supported JSON schema types:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user