Reputation: 1015
I am trying to persist the following document:
class Document {
...
private Map data;
...
}
To store dates, I am using Joda's DateTime object:
data.put("date", DateTime.now());
I am using MongoRepository to persist the data. Writing works great and I can see that mongoDB has a date type.
When I read the data, however, my map does not contain a DateTime, but rather a Date object.
I get it, there is nothing to tell Spring to change the type of the object being read from Date (default), to DateTime as I am not reading concrete types.
Is there a way to tell MongoTemplate/MongoRepository to always convert java.util.Date into Joda DateTime when it's being read?
It's probably something to do with converter, but I have not been able to find an example of how to do this.
Thanks. -AP_
Upvotes: 0
Views: 2805
Reputation: 1015
So, at the end I had to make a little compromise. Due to the fact that I was using a Map to hold arbitrary data, there was no way to invoke a converter since anything can be set to an Object and so Spring Data did not see a need to do any conversion from Date to DateTime.
Instead of using a generic Map, I created my own type which simply extended from Map:
public class EventData extends HashMap<String, Object> {
...
}
Then, I changed my parent class to use EventData, instead of Map:
public class Event implements Serializable {
private final EventData
data;
...
}
Now, having EventData, I could create a custom mapper:
@ReadingConverter
public class ConsumerHandlerEventDataReadConverter implements Converter<DBObject, EventData> {
@Resource
private MappingMongoConverter
mappingMongoConverter;
@Override
public EventData convert(final DBObject source) {
//First, use the general mapping mongo converter to read the source as it would normally be read
//
final EventData
eventData =
mappingMongoConverter
.read(
EventData.class,
source
);
//Now replaces all occurances of Date in EventData with DateTime
//
for (final Map.Entry<String, Object> entry : eventData.entrySet()) {
//Get the value of this entry
//
final Object
entryValue =
entry
.getValue();
//If it's a date, replace with Datetime
//
if (entryValue instanceof Date) {
entry
.setValue(
new DateTime(
entryValue
)
);
}
}
//Return result
//
return
eventData;
}
}
Most of the mapping is done by the standard mapper, so I simply wired up a "different" MappingMongoConverter and used it's read method to read the data. Then, I look for all the Date objects and replace them with DateTime.
To add my converter to Mongo, I register custom conversions type with MappingMongoConverter:
@Configuration
public class ConsumerHandlerMongoDBConfiguration {
...
@Bean
public CustomConversions customConversions() {
return
new CustomConversions(
Arrays.asList(
consumerHandlerEventDataReadConverter()
)
);
}
@Bean
@Description("Mapping mongo converter for the event")
public MappingMongoConverter consumerProcessHandlerMappingMongoConverter() {
final MappingMongoConverter
converter =
new MappingMongoConverter(
mongoDBConfiguration.dbRefResolver(),
mongoDBConfiguration.mongoMappingContext()
);
converter
.setTypeMapper(
mongoDBConfiguration.mongoTypeMapper()
);
converter
.setCustomConversions(
customConversions()
);
return
converter;
}
...
}
Ideally, I would have liked to somehow create a mapper that MappingMongoConverter could ALWAYS consult when dealing with a Date type not specifically in context of EventData, however, looking at the readMap(...) code in Spring Data, I see that this will not happen:
private Object getPotentiallyConvertedSimpleRead(Object value, Class<?> target) {
if (value == null || target == null || target.isAssignableFrom(value.getClass())) {
return value;
}
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
The above code will not call any mappers if target.isAssignableFrom(value.getClass()) and since everything is assignable to an Object, there is just no way to register a custom mapper anyway. The next statement in the code, does indeed check to see if conversions.hasCustomReadTarget, but we never get to it.
Anyhow, documenting this for next time as well for anyone who is trying to do something similar with converters.
-AP_
Upvotes: 2
Reputation:
You must implement your own converter(s) and then override customConversions()
in the class where you enable the MongoDB repositories (@EnableMongoRepositories
).
It also depends on the Spring Data version you are using; so far I remember there was a DateTimeConverters
too.
It would be something like this, but implementing the Spring's Converter<T, K>
interface.
Upvotes: 0