Is it possible to use Spring HATEOAS WebFluxLinkBuilders with Kotlin Coroutines? - spring

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.

You probably forgot to add coroutines to your project. Add these dependencies to your gradle file:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

Related

Quarkus #CacheResult is not working properly

I am trying to use quarkus-cache by following the appropriate quarkus doc. I have the below code setup
#ApplicationScoped
class MyClass {
public result doSomething() {
String remoteData = getRemoteData(url);
}
#CacheResult(cacheName = "myCacheName")
public String getRemoteData(String url) {
return remoteCall(url);
}
}
Usage
// Grpc impl class
// call to Myclass.doSomething()
Execution is not proceeding further when getRemoteData() is called the first time. Also, not getting any error.
Am I missing something?

failed to validate request params in a Spring Boot/Kotlin Coroutines controller

In a SpringBoot/Kotlin Coroutines project, I have a controller class like this.
#RestContollser
#Validated
class PostController(private val posts: PostRepository) {
suspend fun search(#RequestParam q:String, #RequestParam #Min(0) offset:Int, #RequestParam #Min(1) limit:Int): ResponseEntity<Any> {}
}
The validation on the #ResquestBody works as the general Spring WebFlux, but when testing
validating request params , it failed and throws an exception like:
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4165)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
It is not a ConstraintViolationException.
I think this is a bug in the framework when you are using coroutines (update , it is, I saw Happy Songs comment). In summary:
"#Validated is indeed not yet Coroutines compliant, we need to fix that by using Coroutines aware methods to discover method parameters."
The trouble is that the signature of the method on your controller is actually enhanced by Spring to have an extra parameter, like this, adding a continuation:
public java.lang.Object com.example.react.PostController.search(java.lang.String,int,int,kotlin.coroutines.Continuation)
so when the hibernate validator calls getParameter names to get the list of parameters on your method, it thinks there are 4 in total on the request, and then gets an index out of bounds exception trying to get the 4th (index 3).
If you put a breakpoint on the return of this:
#Override
public E get(int index) {
return a[index];
}
and put a breakpoint condition of index ==3 && a.length <4 you can see what is going on.
I'd report it as a bug on the Spring issue tracker.
You might be better off taking an alternative approach, as described here, using a RequestBody as a DTO and using the #Valid annotation
https://www.vinsguru.com/spring-webflux-validation/
Thanks for the happy songs' comments, I found the best solution by now to overcome this barrier from the Spring Github issues#23499.
As explained in comments of this issue and PaulNuk's answer, there is a Continuation will be appended to the method arguments at runtime, which will fail the index computation of the method parameter names in the Hibernate Validator.
The solution is changing the ParameterNameDiscoverer.getParameterNames(Method) method and adding a empty string as the additional parameter name when it is a suspend function.
class KotlinCoroutinesLocalValidatorFactoryBean : LocalValidatorFactoryBean() {
override fun getClockProvider(): ClockProvider = DefaultClockProvider.INSTANCE
override fun postProcessConfiguration(configuration: javax.validation.Configuration<*>) {
super.postProcessConfiguration(configuration)
val discoverer = PrioritizedParameterNameDiscoverer()
discoverer.addDiscoverer(SuspendAwareKotlinParameterNameDiscoverer())
discoverer.addDiscoverer(StandardReflectionParameterNameDiscoverer())
discoverer.addDiscoverer(LocalVariableTableParameterNameDiscoverer())
val defaultProvider = configuration.defaultParameterNameProvider
configuration.parameterNameProvider(object : ParameterNameProvider {
override fun getParameterNames(constructor: Constructor<*>): List<String> {
val paramNames: Array<String>? = discoverer.getParameterNames(constructor)
return paramNames?.toList() ?: defaultProvider.getParameterNames(constructor)
}
override fun getParameterNames(method: Method): List<String> {
val paramNames: Array<String>? = discoverer.getParameterNames(method)
return paramNames?.toList() ?: defaultProvider.getParameterNames(method)
}
})
}
}
class SuspendAwareKotlinParameterNameDiscoverer : ParameterNameDiscoverer {
private val defaultProvider = KotlinReflectionParameterNameDiscoverer()
override fun getParameterNames(constructor: Constructor<*>): Array<String>? =
defaultProvider.getParameterNames(constructor)
override fun getParameterNames(method: Method): Array<String>? {
val defaultNames = defaultProvider.getParameterNames(method) ?: return null
val function = method.kotlinFunction
return if (function != null && function.isSuspend) {
defaultNames + ""
} else defaultNames
}
}
Then declare a new validator factory bean.
#Primary
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun defaultValidator(): LocalValidatorFactoryBean {
val factoryBean = KotlinCoroutinesLocalValidatorFactoryBean()
factoryBean.messageInterpolator = MessageInterpolatorFactory().getObject()
return factoryBean
}
Get the complete sample codes from my Github.

Project Reactor - How to implement Caffeine properly

I am trying to implement a request cache so I can avoid expensive API calls as much as possible.
Currently I have implemented a caching system using Caffeine like so:
#Service
class CacheService {
val playlistCache: Cache<String, Playlist> = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(30, TimeUnit.SECONDS)
.build()
fun queryPlaylistCache(playlistCacheKey: String) =
Mono.justOrEmpty(playlistCache.getIfPresent(playlistCacheKey)).map<Signal<out Playlist>> { Signal.next(it) }
val userSavedSongsCache: Cache<String, List<PlaylistOrUserSavedTrack>> = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(30, TimeUnit.SECONDS).build()
}
#Service
class SpotifyRequestService(
val webClients: WebClients,
val cacheService: CacheService
) {
fun getAPlaylist(Authorization: String, playlistId: String, fields: String?): Mono<Playlist> {
return CacheMono.lookup({ key: String -> cacheService.queryPlaylistCache(key) }, "${playlistId}$fields")
.onCacheMissResume(
webClients.spotifyClientServiceClient.get()
.uri { uriBuilder: UriBuilder ->
uriBuilder.path("/playlists/{playlist_id}")
.queryParam("fields", fields ?: "")
.build(playlistId)
}
.header("Authorization", Authorization)
.retrieve()
.bodyToMono(Playlist::class.java)
)
.andWriteWith { key, value ->
Mono.fromRunnable { value?.get()?.let { cacheService.playlistCache.put(key, it) } } }
}
}
However, from what I have read, it seems like implementing caching this way is not optimal because getting/setting the cache is a blocking operation.
However, in this thread: Cache the result of a Mono from a WebClient call in a Spring WebFlux web application the chosen answer mentions reasons why this is an acceptable use case to use a blocking operation.
Can anybody shed some light as to what the correct solution is?

'Mono.and()' cannot be called with the supplied parameters

I'm trying to run the sample project located here. However, I'm seeing a
Error:(38, 22) Kotlin: None of the following functions can be called with the arguments supplied:
public final fun and(p0: ((Subscriber<in Any!>!) -> Unit)!): Mono<Void!>! defined in reactor.core.publisher.Mono
public final fun and(p0: Publisher<*>!): Mono<Void!>! defined in reactor.core.publisher.Mono
in ApiHandler.kt class in the buildResponse function:
internal class ApiHandler(val geoLocationService: GeoLocationService, val sunriseSunsetService: SunriseSunsetService,
val errorHandler: ErrorHandler) {
private companion object {
const val ADDRESS = "address"
}
internal fun getLocation(request: ServerRequest) =
request.pathVariable(ADDRESS).toMono()
.transform(this::buildResponse)
.transform(this::serverResponse)
.onErrorResume(errorHandler::throwableError)!!
internal fun postLocation(request: ServerRequest) =
request.extract<LocationRequest>()
.map(LocationRequest::address)
.transform(this::buildResponse)
.transform(this::serverResponse)
.onErrorResume(errorHandler::throwableError)!!
internal fun buildResponse(address: Mono<String>) =
address.transform(geoLocationService::fromAddress)
.and(this::sunriseSunset, ::LocationResponse)
internal fun sunriseSunset(geographicCoordinates: GeographicCoordinates) =
geographicCoordinates.toMono().transform(sunriseSunsetService::fromGeographicCoordinates)
internal fun serverResponse(locationResponseMono: Mono<LocationResponse>): Mono<ServerResponse> =
locationResponseMono.flatMap { ok() withBody it }
}
I'm guessing that the Spring API has changed since this code was written, but I can't figure out what to change the .and(...) to.
I think this is linked with a Reactor Core API change in 3.1.0.
Mono.and() is no longer an operator that returns Tuples, but it only cares about completion signals now (Mono<Void>). You should replace that and() operator with a zip or zipWith operator, as suggested in the Reactor release notes.

Async Spring Boot using Kotlin not working

I'm trying to create a Spring Service that performs an operation asynchronously and returns a ListenableFuture. I want the failure callback to be triggered when the operation fails - my attempt to do this is to use AsyncResult.forExecutionException as seen below:
#Service
open class UserClientService {
#Async
fun fetchUser(email: String): ListenableFuture<User> {
val uri = buildUri(email)
val headers = buildHeaders()
try {
val result = restTemplate.exchange(uri, HttpMethod.GET, HttpEntity<Any>(headers), User::class.java)
return AsyncResult.forValue(result.body)
} catch (e: RestClientException) {
return AsyncResult.forExecutionException(e)
}
}
}
The entry-point:
#SpringBootApplication
#EnableAsync
open class UserProxyApplication
fun main(args: Array<String>) {
SpringApplication.run(UserProxyApplication::class.java, *args)
}
The Spring RestController implementation is as follows:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: UserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
val result = DeferredResult<ResponseEntity<User>>(TimeUnit.SECONDS.toMillis(10))
client.fetchUser(email).addCallback(
{ success -> result.setResult(ResponseEntity.ok(success)) },
{ failure -> result.setResult(ResponseEntity(HttpStatus.NOT_FOUND)) }
)
return result;
}
}
Problem is that the failure callback in the UserController is never triggered when an exception is thrown in the UserClientService REST call. Instead, the success callback is triggered with success argument being null.
In Kotlin, I can check if success is null by using success!! - this throws an exception that then does trigger the failure callback with failure argument being the NPE.
Question is how can I trigger the failure callback in the UserController when an exception has occurred in the UserClientService?
Update A it seems that everything is executed on the same thread "http-nio-8080-exec-XXX" regardless of whether I use #Async or not -- see comments.
This all works if:
A) the method fetchUser is declared open, i.e. not final so that Spring can proxy the call
...or...
B) you create an interface IUserClientService and use that in the constructor of the UserController:
interface IUserClientService {
fun fetchUser(email: String): ListenableFuture<User>
}
Now the UserClientService implements the interface:
#Service
open class UserClientService : IUserClientService {
#Async
override fun fetchUser(email: String): ListenableFuture<User> {
// ... rest as shown in question ...
And finally the UserController:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: IUserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
// ... rest as shown in question ...
Not sure if this is because I'm using Kotlin. The examples that I've seen don't require implementing an interface.

Resources