I might be asking for trouble with this setup but I am trying to get MongoDB with MongoJack to work with Kotlin classes. I've managed to overcome a few problems already for which I will outline my solutions if others have the same problem but I am still struggling with one issue.
Kotlin class:
data class TestDocument (
@ObjectId @JsonProperty("_id") var id:String? = null,
@JsonProperty("name") var name:String,
var date: Date = Date(),
var decimal: BigDecimal = BigDecimal.valueOf(123.456)
)
If I use the default setup (using the ObjectMapper mongoJack configured) like this:
object MongoClientTestDefault {
val log: Log = Logging.getLog(MongoClientTest::class.java)
val mongoClient : MongoClient
val database: MongoDatabase
val testCol: MongoCollection<TestDocument>
init {
val mongoUrl = "mongodb://localhost:27017/"
log.info("Initialising MongoDB Client with $mongoUrl")
try {
mongoClient = MongoClients.create(mongoUrl)
database = mongoClient.getDatabase("unitTest")
testCol = JacksonMongoCollection.builder()
.build(
mongoClient,
"unitTest",
"testCol",
TestDocument::class.java,
UuidRepresentation.STANDARD
)
log.info("Successfully initialised MongoDB client")
}catch(e: Exception){
log.error(Strings.EXCEPTION,e)
log.error("Mongo URL: ", mongoUrl)
throw e //we need to throw this as otherwise it complains that the val fields haven't been initialised
}
}
}
Then the serialization (or persisting to Mongo) works ok but the deserialization from Mongo to the Kotlin class fails because without the Kotlin jackson module Jackson and MongoJack don't know how to handle the Kotlin constructor it seems.
To fix that you have to use the jacksonObjectMapper() and pass that to the builder:
testCol = JacksonMongoCollection.builder()
.withObjectMapper(jacksonObjectMapper)
.build(
mongoClient,
"unitTest",
"testCol",
TestDocument::class.java,
UuidRepresentation.STANDARD
)
That will solve the deserialization but now the Dates are causing problems. So in order to fix that I do the following
object MongoClientTest {
val log: Log = Logging.getLog(MongoClientTest::class.java)
val mongoClient : MongoClient
val database: MongoDatabase
val testCol: MongoCollection<TestDocument>
var jacksonObjectMapper = jacksonObjectMapper()
init {
val mongoUrl = "mongodb://localhost:27017/"
MongoJackModule.configure(jacksonObjectMapper)
log.info("Initialising MongoDB Client with $mongoUrl")
try {
mongoClient = MongoClients.create(mongoUrl)
database = mongoClient.getDatabase("unitTest")
testCol = JacksonMongoCollection.builder()
.withObjectMapper(jacksonObjectMapper)
.build(
mongoClient,
"unitTest",
"testCol",
TestDocument::class.java,
UuidRepresentation.STANDARD
)
log.info("Successfully initialised MongoDB client")
}catch(e: Exception){
log.error(Strings.EXCEPTION,e)
log.error("Mongo URL: ", mongoUrl)
throw e //we need to throw this as otherwise it complains that the val fields haven't been initialised
}
}
}
I take the jacksonObjectMapper and call MongoJackModule.configure(jacksonObjectMapper) and then pass it to the builder.
Now serialization (persiting to Mongo) and deserialization (loading from Mongo and converting to Pojo) works but there is one Exception thrown (which seems to be handled).
var testDoc = TestDocument(name="TestName")
MongoClientTest.testCol.drop()
var insertOneResult = MongoClientTest.testCol.insertOne(testDoc)
This call works and it creates the document in Mongo but this exception is printed:
java.lang.UnsupportedOperationException: Cannot call setValue() on constructor parameter of com.acme.MongoJackTest$TestDocument
at com.fasterxml.jackson.databind.introspect.AnnotatedParameter.setValue(AnnotatedParameter.java:118)
at org.mongojack.internal.stream.JacksonCodec.lambda$getIdWriter$4(JacksonCodec.java:120)
at org.mongojack.internal.stream.JacksonCodec.generateIdIfAbsentFromDocument(JacksonCodec.java:77)
at com.mongodb.internal.operation.Operations.bulkWrite(Operations.java:450)
at com.mongodb.internal.operation.Operations.insertOne(Operations.java:375)
at com.mongodb.internal.operation.SyncOperations.insertOne(SyncOperations.java:177)
at com.mongodb.client.internal.MongoCollectionImpl.executeInsertOne(MongoCollectionImpl.java:476)
at com.mongodb.client.internal.MongoCollectionImpl.insertOne(MongoCollectionImpl.java:459)
at com.mongodb.client.internal.MongoCollectionImpl.insertOne(MongoCollectionImpl.java:453)
at org.mongojack.MongoCollectionDecorator.insertOne(MongoCollectionDecorator.java:528)
It is trying to create and set the _id on the TestDocument but struggles with the primary constructor of the Kotlin class. I've tried to add a 'default aka empty' constructor:
data class TestDocument (
@ObjectId @JsonProperty("_id") var id:String? = null,
@JsonProperty("name") var name:String,
var date: Date = Date(),
var decimal: BigDecimal = BigDecimal.valueOf(123.456)
){
@JsonCreator constructor(): this(name = "")
}
But that doesn't fix it.
Any ideas how I could fix this? This is the code in MongoJack that is catching the exception:
private Consumer<BsonObjectId> getIdWriter(final T t) {
final Optional<BeanPropertyDefinition> maybeBpd = getIdElementSerializationDescription(t.getClass());
return maybeBpd.<Consumer<BsonObjectId>>map(beanPropertyDefinition -> (bsonObjectId) -> {
try {
if (bsonObjectId != null) {
beanPropertyDefinition.getMutator().setValue(
t,
extractIdValue(bsonObjectId, beanPropertyDefinition.getRawPrimaryType())
);
}
} catch (Exception e) {
e.printStackTrace();
}
}).orElseGet(() -> (bsonObjectId) -> {
});
}
I guess it could just be ignored but that could flood the logs.