I am trying to translate the following reactive code into kotlin coroutines:
@GetMapping
fun getAllTodosMono(): Mono<CollectionModel<TodoItem>> =
repository
.findAll()
.collectList()
.flatMap { mkSelfLinkMono(it) }
private fun mkSelfLinkMono(list: List<TodoItem>): Mono<CollectionModel<TodoItem>> {
val method = methodOn(Controller::class.java).getAllTodosMono()
val selfLink = linkTo(method).withSelfRel().toMono()
return selfLink.map { CollectionModel.of(list, it) }
}
Coroutine Version:
@GetMapping
suspend fun getAllTodosCoroutine(): CollectionModel<TodoItem> =
repository
.findAll()
.collectList()
.awaitSingle()
.let { mkSelfLinkCoroutine(it) }
private suspend fun mkSelfLinkCoroutine(list: List<TodoItem>): CollectionModel<TodoItem> {
val method = methodOn(Controller::class.java).getAllTodosCoroutine()
val selfLink = linkTo(method).withSelfRel().toMono().awaitSingle()
return CollectionModel.of(list, selfLink)
}
However, I get a runtime error when trying to run the code.
java.lang.ClassCastException: class org.springframework.hateoas.server.core.LastInvocationAware$$EnhancerBySpringCGLIB$$d8fd0e7e cannot be cast to class org.springframework.hateoas.CollectionModel (org.springframework.hateoas.server.core.LastInvocationAware$$EnhancerBySpringCGLIB$$d8fd0e7e is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @62b177e9; org.springframework.hateoas.CollectionModel is in unnamed module of loader 'app')
I suspect methodOn(...) does not support suspend functions. The only solution that actually works is to build the link by hand instead of using the linkTo(...) function:
private fun mkSelfLink(list: List<TodoItem>): CollectionModel<TodoItem> {
return Link
.of("/api/v1/todos")
.withSelfRel()
.let { CollectionModel.of(list, it) }
}
However, I lose the ability to link to existing endpoints in my REST controller and also the host that is automagically added to the link uri.
Am I missing something?
EDIT: Here is the link to my github repo: https://github.com/enolive/kotlin-coroutines/tree/master/todos-coroutini
If you paste the following code sample into the TodoController replacing the original getTodo(...) method, you can see the failure I described above.
private suspend fun Todo.withSelfLinkByBuilder(): EntityModel<Todo> {
val method = methodOn(Controller::class.java).getTodo(id!!)
val selfLink = linkTo(method).withSelfRel().toMono().awaitSingle()
return EntityModel.of(this, selfLink)
}
@GetMapping("{id}")
suspend fun getTodo(@PathVariable id: ObjectId) =
repository.findById(id)?.withSelfLinkByBuilder()
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
Well, I found a solution, I don't know if is it a satisfactory one, but it works, none of the less.
By simple chaining the function calls together the runtime appears to work as intended:
private suspend fun mkSelfLinkCoroutine(list: List<TodoItem>): CollectionModel<TodoItem> {
val selfLink = linkTo(methodOn(Controller::class.java)
.getAllTodosCoroutine())
.withSelfRel()
.toMono()
.awaitSingle()
return CollectionModel.of(list, selfLink)
}
This is really strange, but it is what it is.