Unable to iterate over stream inside use{} block - spring

I just want to print name of every user stored in database.
I am using this repository:
#Repository
interface User: JpaSpecificationExecutor<User>, PagingAndSortingRepository<User, Long> {
#Query("from User")
fun findAllUsers(): Stream<User>
}
inside this Service:
#Service
class MyService(val user: User) {
private val log = LoggerFactory.getLogger(javaClass)
#Transactional(readOnly = true)
fun printNames() {
log.info("here")
user.findAllUsers().use { users ->
users.map { it.firstName }
}
}
}
and it prints only here in console, but no name.
It seems like map() automatically closed the stream but I don`t know why and how to workaround it. When I put inside of use{} block only log.info(users.count()) it prints number of users stored in database. So there is a user I can print.
My question is, how can I print all names from the given stream?

Kotlin's use function is just a short-hand way of executing some code (your closure function) and then call close on the receiver (in this case the Stream) once done.
What you called users is actually the Stream<User>returned by your repository, so basically your code is just calling users.map {...}. Now, the map operator is an intermediate operator, and since Java streams are lazy, they won't actually do anything until you call a terminal operator (such as .collect or .forEach).
Assuming you want to print the user, try with:
user.findAllUsers().use {
it.forEach { println(it.firstName) }
}
Full working example (without Spring data):
import java.util.stream.Stream
// simulate a repository
fun findAllUsers() = Stream.of("First", "Second", "Third")
fun printNames() {
findAllUsers().use {
it.forEach(::println)
}
}
fun main() {
printNames()
}
Prints:
First
Second
Third

Related

Reactive way of returning `Map<Long,T>`

I am learning reactive. In an MVC project I have a service method (and the controller has the same signature) like this:
#Override
public Map<Long, Question> getQuestions() {
List<Question> questions = questionRepo.findAllByType(Type.A);
return questions.stream()
.collect(Collectors.toMap(q -> q.getId(), q -> q));
}
Resulting in something similar to this:
{
1: {id: 1, ...}
2: {id: 2, ...}
...
}
Now, switching to reactive and kotlin coroutines. What is the proper way to implement this in a reactive way?
This is the signature of the repository:
interface QuestionRepository : CoroutineCrudRepository<Question, Long> {
#Query("select * from Question q where type = :type")
fun findAllByType(type: Type): Flow<Question>
}
Approaches
From what I think so far using Mono<Map<Long,Question>> seems to make no sense as it would require to block for building the inner map.
Flow<Map<Long,Question>> Does not make sense either, because we do not populate multiple maps.
So my best approach for now is not using a Map...
override fun getQuestions(): Flow<Question> {
return questionRepo.findAllByType(Type.A)
}
...but this would require to change the frontend code (it now needs to convert the list to a map).
I also think of
override fun getQuestions(): Flow<Pair<Long?,Question>> {
return questionRepo.findAllByType(Type.A).map { it.id to it }
}
but this would require the frontend to change as well, because the output would look like
[{"first":1,"second":{"id":1, ...}]
Are there other, better approaches? How would you implement it?
UPDATE
added repository.
Assuming the Flow emits elements one at a time that you want to put into a single Map in the client code, then you can collect them into a MutableMap like this and return it.
suspend fun getQuestions(): Map<Long, Question> {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
return map
}
If your downstream client code is not expecting a suspend function, I guess you need to wrap this in runBlocking, and presumably the downstream code is already handling the fact that this is a long-running function call.
override fun getQuestions(): Map<Long, Question> = runBlocking {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
map
}

Spring WebFlux flatmap return Mono<Void>

I have a WebApplication written in Kotlin. It is a chat kinda like Discord.
I have a function which does several things. The function itself is used to delete the account of the user but while doing this, it is also deleting the messages which he have ever written off the database, he gets removed of all the groups in the database and in the groups where he was the admin a new admin is chosen.
And this function does look like this:
fun deleteAccount(userId: String) : Flux<Void>{
return groupRepository.findAllByAdmin(userId)
.flatMap{
if(it.users.size<1) groupRepository.deleteById(it._id)
it.admin = it.users[0]
it.users.remove(userId)
groupRepository.save(it)
}
.flatMap {
messageRepository.deleteMessageByUserId(userId)
userRepository.deleteById(userId)
}
}
My repositories look like this:
#Repository
interface GroupRepository : ReactiveCrudRepository<GroupRequest, String> {
fun findByName(name: String): Mono<GroupRequest>
fun existsByName(name: String): Mono<Boolean>
fun findAllByAdmin(admin:String) : Flux<GroupRequest>
}
#Repository
interface MessageRepository : ReactiveCrudRepository<Message, String> {
fun findMessageByGroupId(groupId: String): Flux<Message>
fun deleteMessageByUserId(userId:String) : Mono<Void>
}
So the last 2 statements in my function return a void Mono.
The problem is it does not matter which statement is executed first, only one of the 2 statements is executed and I tried several things now including Flux.zip it does not work completely. If I put the deleteMessages first, everything works except the part where the user is deleted of the database. Where is the problem in this function and how can I fix it?
I fixed it by zipping both statements in the second flatMap, looks like this:
fun deleteAccount(userId: String) : Flux<Void>{
return groupRepository.findAllByAdmin(userId)
.flatMap{
if(it.users.size<1) groupRepository.deleteById(it._id)
it.admin = it.users[0]
it.users.remove(userId)
groupRepository.save(it)
}
.flatMap {
Mono.zip(messageRepository.deleteMessageByUserId(userId),userRepository.deleteById(userId)).map {
it.t1
}
}
}
You might be looking for Mono.when() if all you care about is the completion signal.

Mocking an extension function with Mockito

How can I test an extension function with Mockito? It doesn't seem to work nicely.
This is my extension function
fun <T> CrudRepository<T, String>.findOneById(id: String): T? {
val o = findById(id)
return if (o.isPresent) o.get() else null
}
And this is what I'm trying to test
#Test
fun getIslandById() {
//given
BDDMockito.given(islandRepository.findOneById("islandId1"))
.willReturn(IslandEntity(tileList, "1", "islandId1")) //findOneById is my extension function
//when
val island = islandService.getIslandById("islandId1")
//then
Assertions.assertThat(island?.id).isEqualTo("islandId1")
}
But the preceeding test throws the following error
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
IslandEntity cannot be returned by findById()
findById() should return Optional
Any ideas?
Instance extension functions can be mocked like this with a little help of mockito-kotlin:
data class Bar(thing: Int)
class Foo {
fun Bar.bla(anotherThing: Int): Int { ... }
}
val bar = Bar(thing = 1)
val foo = mock<Foo>()
with(foo) {
whenever(any<Bar>().bla(any()).doReturn(3)
}
verify(foo).apply {
bar.bla(anotherThing = 2)
}
As I said in a comment above, in the bytecode extension functions are nothing more than static functions which accept receiver as a first argument. Therefore you can't mock an extension function with a Mockito since it is not able to mock static functions.
What you can do, in case that findById(id) is implemented by Repository and not another extension function, is next:
Mock return value of findById(id) instead.
Take a look at the sample code below:
#Test
fun getIslandById() {
//given
BDDMockito.given(islandRepository.findById("islandId1"))
.willReturn(Optional.of(IslandEntity(tileList, "1", "islandId1"))) //mock findById function
//when
val island = islandService.getIslandById("islandId1")
//then
Assertions.assertThat(island?.id).isEqualTo("islandId1")
}
This way you are indirectly mocking your extension by providing it a mocked value that you want it to operate on.
Note: Error that you posted above says that your findById(id) should return an optional. So, wrap your return value of findById(id) function with an optional by calling Optional.of(result).

How to check if Mono is empty?

I'm developing a app with Spring Boot 2.0 and Kotlin using the WebFlux framework.
I want to check if a user id exits before save a transaction. I'm stucked in a simple thing like validate if a Mono is empty.
fun createTransaction(serverRequest: ServerRequest) : Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java))
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
}
It is possible to do what I want?
The techniques that allow checking whether Flux/Mono is empty
Using operators .switchIfEmpty/.defaultIfEmpty/Mono.repeatWhenEmpty
Using mentioned operators you will be able to react to the case when Stream has been completed without emitting any elements.
First of all, remember that operators such .map, .flatMap, .filter and many others will not be invoked at all if there no onNext has been invoked.
That means that in your case next code
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
will not be invoked at all, if transaction will be empty.
In case if there is a requirement for handling cases when your flow is empty, you should consider operators like next in the following manner:
transaction
.flatMap(it -> {
val user = userRepository.findById(it.userId)
})
.swithIfEmpty(Flux.defer(() -> Flux.just(badRequest())));
Actual solution
Also, I have noted that you created two sub-flows from the main transaction. Actually, following code will not be executed at all:
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
and will be only executed the last one, which is returned from the method. That happens because you ain't subscribed using operator .subscribe(...).
The second point, you can't subscribe to the same request body more the one time (kind of limitation for WebClient's reponse). Thus you are required to share your request body in the next way, so completed example will be:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java)).cache()
transaction
.flatMap { userRepository.findById(it.userId) }
.flatMap { transaction.flatMap { transactionRepository.save(it) } }
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
.switchIfEmpty(transaction.flatMap { ServerResponse.badRequest().syncBody("missed User for transaction " + it.id) })
}
Or more simple case without sharing transaction flow but using Tuple:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val emptyUser = !User()
val transaction = serverRequest.body<Mono<Transaction>>(BodyExtractors.toMono(Transaction::class.java))
transaction
.flatMap { t ->
userRepository.findById(t.userId)
.map { Tuples.of(t, it) }
.defaultIfEmpty(Tuples.of(t, emptyUser))
}
.flatMap {
if (it.t2 != emptyUser) {
transactionRepository.save(it.t1)
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
} else {
ServerResponse.badRequest().syncBody("missed User for transaction " + it.t1.id)
}
}
}
You can check it using the Mono's provided method hasElement() which is analogous to Optional's isPresent(). The method definition is :
Mono<Boolean> hasElement()
for more details checkout : project reactor documentation
In case you have to perform some action based on this value you can further use switchIfEmpty() to provide with alternate publisher.
Let me start by saying I am a newbie on reactive (java) and on this forum.
I think you cannot really check in this code if a mono is empty because a mono represents code that will be executed later on, so in this code body you won't know yet if its is empty. Does that make sense?
I just wrote something similar in Java which seems to work (but not 100% this is the best approach either):
public Mono<ServerResponse> queryStore(ServerRequest request) {
Optional<String> postalCode = request.queryParam("postalCode");
Mono<ServerResponse> badQuery = ServerResponse.badRequest().build();
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
if (!postalCode.isPresent()) { return badQuery; }
Flux<Store> stores = this.repository
.getNearByStores(postalCode.get(), 5);
return ServerResponse.ok().contentType(APPLICATION_JSON)
.body(stores, Store.class)
.switchIfEmpty(notFound);
}
We can use switchIfEmpty method for this
Below example, I'm checking if the user exists with email if not then add it
userRepository.findByEmail(user.getEmail())
.switchIfEmpty(s -> {
user.setStatus("InActive");
String encodedPassword = DigestUtils.sha256Hex(user.getPassword());
user.setPassword(encodedPassword);
userRepository.save(user).subscribe();
s.onComplete();
}).then(Mono.just(user));
Use Mono with Optional:
return findExistingUserMono
.map(Optional::of)
.defaultIfEmpty(Optional.empty())
.flatMap(optionalUser -> {
if(optionalUser.isPresent()) {
return Mono.error('xxxx');
}
return this.userService.create(optionalUser.get());
});
This way it will always emit Optional value so that the stream will never break.

How to Access Mono<T> While Handling Exception with onErrorMap()?

In data class I defined the 'name' must be unique across whole mongo collection:
#Document
data class Inn(#Indexed(unique = true) val name: String,
val description: String) {
#Id
var id: String = UUID.randomUUID().toString()
var intro: String = ""
}
So in service I have to capture the unexpected exception if someone pass the same name again.
#Service
class InnService(val repository: InnRepository) {
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ err -> InnAlreadyExistedException("The inn already existed", err) }
)
}
This is OK, but what if I want to add more info to the exceptional message like "The inn named '$it.name' already existed", what should I do for transforming exception with enriched message.
Clearly, assign Mono<Inn> to a local variable at the beginning is not a good idea...
Similar situation in handler, I'd like to give client more info which derived from the customized exception, but no proper way can be found.
#Component
class InnHandler(val innService: InnService) {
fun create(req: ServerRequest): Mono<ServerResponse> {
return innService
.create(req.bodyToMono<Inn>())
.flatMap {
created(URI.create("/api/inns/${it.id}"))
.contentType(MediaType.APPLICATION_JSON_UTF8).body(it.toMono())
}
.onErrorReturn(
InnAlreadyExistedException::class.java,
badRequest().body(mapOf("code" to "SF400", "message" to t.message).toMono()).block()
)
}
}
In reactor, you aren't going to have the value you want handed to you in onErrorMap as an argument, you just get the Throwable. However, in Kotlin you can reach outside the scope of the error handler and just refer to inn directly. You don't need to change much:
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ InnAlreadyExistedException("The inn ${inn.name} already existed", it) }
)
}

Resources