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 index 06033347e..2f56c2dce 100644 --- 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 @@ -24,6 +24,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.bson.Document; + import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -40,7 +41,7 @@ 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.data.util.ClassTypeInformation; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -168,7 +169,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { JsonSchemaProperty schemaProperty; if (isCollection(property)) { - schemaProperty = createSchemaPropertyForCollection(fieldName, property, required); + schemaProperty = createArraySchemaProperty(fieldName, property, required); } else if (property.isMap()) { schemaProperty = createSchemaProperty(fieldName, Type.objectType(), required); } else if (ClassUtils.isAssignable(Enum.class, targetType)) { @@ -180,49 +181,52 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { return applyEncryptionDataIfNecessary(property, schemaProperty); } - private JsonSchemaProperty createSchemaPropertyForCollection(String fieldName, MongoPersistentProperty property, + private JsonSchemaProperty createArraySchemaProperty(String fieldName, MongoPersistentProperty property, boolean required) { ArrayJsonSchemaProperty schemaProperty = JsonSchemaProperty.array(fieldName); - if (property.getActualType() != Object.class) { - - MongoPersistentEntity persistentEntity = mappingContext - .getPersistentEntity(property.getTypeInformation().getComponentType()); - - if (persistentEntity == null) { - - if (ClassUtils.isAssignable(Enum.class, property.getActualType())) { - - List possibleValues = new ArrayList<>(); - - for (Object enumValue : EnumSet.allOf((Class) property.getActualType())) { - possibleValues.add(converter.convertToMongoType(enumValue)); - } - - Class targetType = possibleValues.isEmpty() ? property.getActualType() - : possibleValues.iterator().next().getClass(); - schemaProperty = schemaProperty - .items(Collections.singleton(JsonSchemaObject.of(targetType).possibleValues(possibleValues))); - } else { - schemaProperty = schemaProperty.items(Collections.singleton(JsonSchemaObject.of(property.getActualType()))); - } - } else { - - List nestedProperties = computePropertiesForEntity(Collections.emptyList(), - persistentEntity); - - if (!nestedProperties.isEmpty()) { - schemaProperty = schemaProperty.items(Collections - .singleton(JsonSchemaObject.object().properties(nestedProperties.toArray(new JsonSchemaProperty[0])))); - } - } + if (isSpecificType(property)) { + schemaProperty = potentiallyEnhanceArraySchemaProperty(property, schemaProperty); } return createPotentiallyRequiredSchemaProperty(schemaProperty, required); } - @Nullable + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ArrayJsonSchemaProperty potentiallyEnhanceArraySchemaProperty(MongoPersistentProperty property, + ArrayJsonSchemaProperty schemaProperty) { + + MongoPersistentEntity persistentEntity = mappingContext + .getPersistentEntity(property.getTypeInformation().getRequiredComponentType()); + + if (persistentEntity != null) { + + List nestedProperties = computePropertiesForEntity(Collections.emptyList(), persistentEntity); + + if (nestedProperties.isEmpty()) { + return schemaProperty; + } + + return schemaProperty + .items(JsonSchemaObject.object().properties(nestedProperties.toArray(new JsonSchemaProperty[0]))); + } + + if (ClassUtils.isAssignable(Enum.class, property.getActualType())) { + + List possibleValues = getPossibleEnumValues((Class) property.getActualType()); + + return schemaProperty + .items(createSchemaObject(computeTargetType(property.getActualType(), possibleValues), possibleValues)); + } + + return schemaProperty.items(JsonSchemaObject.of(property.getActualType())); + } + + private boolean isSpecificType(MongoPersistentProperty property) { + return !ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType()); + } + private JsonSchemaProperty applyEncryptionDataIfNecessary(MongoPersistentProperty property, JsonSchemaProperty schemaProperty) { @@ -252,15 +256,12 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required); } + @SuppressWarnings({ "unchecked", "rawtypes" }) private JsonSchemaProperty createEnumSchemaProperty(String fieldName, Class targetType, boolean required) { - List possibleValues = new ArrayList<>(); + List possibleValues = getPossibleEnumValues((Class) targetType); - for (Object enumValue : EnumSet.allOf((Class) targetType)) { - possibleValues.add(converter.convertToMongoType(enumValue)); - } - - targetType = possibleValues.isEmpty() ? targetType : possibleValues.iterator().next().getClass(); + targetType = computeTargetType(targetType, possibleValues); return createSchemaProperty(fieldName, targetType, required, possibleValues); } @@ -271,14 +272,20 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { JsonSchemaProperty createSchemaProperty(String fieldName, Object type, boolean required, Collection possibleValues) { + TypedJsonSchemaObject schemaObject = createSchemaObject(type, possibleValues); + + return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required); + } + + private TypedJsonSchemaObject createSchemaObject(Object type, Collection possibleValues) { + TypedJsonSchemaObject schemaObject = type instanceof Type ? JsonSchemaObject.of(Type.class.cast(type)) : JsonSchemaObject.of(Class.class.cast(type)); if (!CollectionUtils.isEmpty(possibleValues)) { schemaObject = schemaObject.possibleValues(possibleValues); } - - return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required); + return schemaObject; } private String computePropertyFieldName(PersistentProperty property) { @@ -309,23 +316,34 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType(); } + private static Class computeTargetType(Class fallback, List possibleValues) { + return possibleValues.isEmpty() ? fallback : possibleValues.iterator().next().getClass(); + } + + private > List getPossibleEnumValues(Class targetType) { + + EnumSet enumSet = EnumSet.allOf(targetType); + List possibleValues = new ArrayList<>(enumSet.size()); + + for (Object enumValue : enumSet) { + possibleValues.add(converter.convertToMongoType(enumValue)); + } + + return possibleValues; + } + private static boolean isCollection(MongoPersistentProperty property) { return property.isCollectionLike() && !property.getType().equals(byte[].class); } static JsonSchemaProperty createPotentiallyRequiredSchemaProperty(JsonSchemaProperty property, boolean required) { - - if (!required) { - return property; - } - - return JsonSchemaProperty.required(property); + return required ? JsonSchemaProperty.required(property) : property; } class PropertyContext implements JsonSchemaPropertyContext { - private String path; - private MongoPersistentProperty property; + private final String path; + private final MongoPersistentProperty property; public PropertyContext(String path, MongoPersistentProperty property) { this.path = path;