Description
For a while we've had a class used in a lot of our mongo collections, FieldChange
, which just recorded when a field changed. This is used in collections e.g.
class FieldChange {
String field;
String oldValue;
String newValue;
}
class ExampleClass {
List<FieldChange> changes = new ArrayList<>();
}
We can see in mongo that _class
has not been written e.g. for ExampleClass
in mongo:
{
"changes" : [
{
"field" : "name",
"oldValue": "Bob",
"newValue": "Robert"
}
]
}
We now are trying to enrich our model, so FieldChange
becomes an abstract class, and we have many variations, but what was FieldChange
is now SimpleFieldChange
.
abstract class FieldChange {
String field;
}
class SimpleFieldChange extends FieldChange {
String field;
String oldValue;
String newValue;
}
But now we cannot load the existing data from mongo
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate example.FieldUpdateEvent using constructor public example.FieldUpdateEvent() with arguments
We've been investigating specifying details via annotations we assumed would work, e.g.
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include=JsonTypeInfo.As.PROPERTY,
property = "_class",
defaultImpl = SimpleFieldChange.class
)
abstract class FieldChange {
String field;
}
And a few variations on the fields as well as classes, but nothing seemed to work. We finally debugged, and can see that Mongo loads up items in the collection using DefaultMongoTypeMapper
, which extends DefaultTypeMapper
.
can see this has method that starts:
public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> basicType) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(basicType, "Basic type must not be null");
Class<?> documentsTargetType = getDefaultedTypeToBeUsed(source);
if (documentsTargetType == null) {
return basicType;
}
Class<T> rawType = basicType.getType();
...
effectively what would be nice is if getDefaultedTypeToBeUsed(source)
had some way to then lookup by basicType
if nothing in source was found for the mapping. No simple way to hook into this though, since getDefaultedTypeToBeUsed
only takes source.
Would be nice if annotations on the basicType
could be inspected for default implementation, this would seem to fit in with expectations.
Otherwise if their was a pluggable way to define default mappings, would seem could be put into above block, e.g.
public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> basicType) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(basicType, "Basic type must not be null");
Class<?> documentsTargetType = getDefaultedTypeToBeUsed(source);
final Class<T> rawType = basicType.getType();
if (documentsTargetType == null) {
// have some fallback type map that means we could lookup FieldUpdateEvent and tell it to act like it found ValueFieldUpdate
documentsTargetType = customFallback.lookup(rawType.getName());
if (documentsTargetType == null) {
return basicType;
}
}
...
We're so unsure how to do this, so we're now looking to run a migration on all our data to add _class
information, but feels like their should be a simpler way to configure this in code.