Description
I am trying to setup automatic Client-Side Field Level Encryption via a generated Json Schema using the new@Encrypted
annotation.
Doc. reference : https://docs.spring.io/spring-data/mongodb/docs/3.3.x/reference/html/#mongo.jsonSchema
Example 90. Client-Side Field Level Encryption via Json Schema
I stumbled into a severe limitation that impacts the schema generation through MappingMongoJsonSchemaCreator : embedded documents using subclasses or interfaces implementations have their attributes partially ignored.
Here's a test that highlights the issue :
@Test
public void test_embedded_subclasses_ignored() {
final MongoJsonSchema schema = MongoJsonSchemaCreator.create().createSchemaFor(Doc.class);
final Document schemaDocument = schema.schemaDocument();
final Document embeddedDocDocument = schemaDocument.get("properties", Document.class).get("embeddedDoc", Document.class)
.get("properties", Document.class);
final Document embeddedDocInterfaceDocument = schemaDocument.get("properties", Document.class).get("embeddedDocInterface", Document.class)
.get("properties", Document.class);
assertAll(() -> assertThat(embeddedDocDocument).isNotNull().containsKeys("a", "childBAttr", "childCAttr"),
() -> assertThat(embeddedDocInterfaceDocument).isNotNull().containsKeys("interfaceAttr", "interfaceAttrB"));
}
@Data
static class Doc {
String attr1;
A embeddedDoc;
AnInterface embeddedDocInterface;
}
interface AnInterface {
String getInterfaceAttr();
}
@Data
static class InterfaceImpl implements AnInterface {
@Encrypted
String interfaceAttr;
@Encrypted
String interfaceAttrB;
}
@Data
static class B extends A {
@Encrypted
String childBAttr;
}
@Data
static class C extends A {
@Encrypted
String childCAttr;
}
@Data
static abstract class A {
@Encrypted
String a;
}
The resulting schema contains definitions for attributes attr1, embeddedDoc.a and embeddedDocInterface.interfaceAttr only.
I'd expect the schema to contain all a, childBAttr and childCAttr properties defined for 'embeddedDoc', and interfaceAttr + interfaceAttrB for the 'embeddedDocInterface'.
The issue originates here as it will only consider the base class or interface to resolve attributes :
MappingMongoJsonSchemaCreator#computePropertiesForEntity
(L129)
...
List<JsonSchemaProperty> schemaProperties = new ArrayList<>();
for (MongoPersistentProperty nested : nestedProperties) {
...
Here's a potential solution I'm using in the meantime with a custom version of the class, getSubClasses() being any implementation for extracting the subclasses of a given class or interface :
...
List<JsonSchemaProperty> schemaProperties = new ArrayList<>();
final Set<? extends MongoPersistentEntity<?>> subclassesEntities = getSubClasses(entity.getType())
.stream().map(mappingContext::getRequiredPersistentEntity)
.collect(Collectors.toSet());
final Set<MongoPersistentProperty> nestedProperties =
subclassesEntities.isEmpty() ? new HashSet<>(IteratorUtils.toList(entity.iterator())) :
subclassesEntities.stream().flatMap(subClassEntity -> IteratorUtils.toList(subClassEntity.iterator()).stream())
.collect(Collectors.toSet());
for (MongoPersistentProperty nested : nestedProperties) {
...