Quarkus Panache Reactive Mongo DB: Update with non existent id returns no failure - quarkus

I made a litte reactive REST service with quarkus, mutiny and panache and discovered some odd behaviour. Everything works as expected when I update an instance with an id that is in the database. But when I change the id to something not existent, I would expect that a failure is raised. Instead I get nothing, and just work on the item that i provided.
#ApplicationScoped
class MyService #Inject constructor(private val myRepository: MyRepository) {
fun update(myEntity: MyEntity): Uni<UUID> {
return myRepository.update(myEntity)
.onItem()
.transform { item -> item.uuid }
}
fun persist(myEntity: MyEntity): Uni<UUID> {
return myRepository.persist(myEntity).onItem().transform { item -> item.uuid }
}
}
class MyEntity {
#BsonId
var uuid: UUID? = null
...
}
So if I persist my first entity with uuid A, the entity is inserted and i return uuid A to the caller. When I call update with uuid A, the entity is updated, and A is returned. But when I call update with an entity with the non existent uuid B, nothing happens in the DB, but i still will get my uuid B without any failure an return it.
Can anybody tell me what I am doing wrong, or how I can get the information if an update was done or not?
Edit:
And I also dont understand where this item comes from. My call runs in this method:
So as I see, the retrieved item is even discarded and null is returned. So how can my updateCall bring up any item?

Related

Spring + Hibernate: Select from repository while it's updating an entry

I've just run into a problem, where I'm trying to select (repo.findById(id)) an object from the database using it's id, while it's being updated (Hibernate's onPreUpdate and onPostUpdate methods in PreUpdateEventListener and PostUpdateEventListener interfaces), but it's throwing a NullPointerException for me.
Perhaps it's easier if I explain it this way:
I have an object with status "PENDING", if it's being changed to "CONFIRMED", I want to check what the previous status was in the onPreUpdate method by doing this:
#Override
public boolean onPreUpdate(PreUpdateEvent preUpdateEvent)
{
final Object entity = preUpdateEvent.getEntity();
if (entity instanceof Status)
{
Status status = (Status) entity;
//Method below throws NullPointerException
Status statusFromRepo = statusRepo.findByStatusId(status.getStatusId());
}
return false;
}
But since statusRepo is already updating this object in the database, am I not able to do anything to get the object BEFORE it's updated? PreUpdateEvent contains the already "updated" version which is going to be saved in the database.

Spring boot: mockMvc testing controller: Expected: Entity, Actuall: null

How looks my json:
event object
{
...
"game": {
//game fields
}
}
...
}
I am trying to do:
event.setGame(new Game());
And check if there is my value by mockMvc
.andExpect(jsonPath("$.game").value(event.getGame()))
But i am getting error:
java.lang.AssertionError: JSON path "$.game"
Expected : Event(id= null, name= null, ...)
Actual :null
Why i am getting null, if i should get just empty game?
P.S. even if i set fields to game, i will get null
I make .andDo(print), and get :
Body = // event
{
"id":"5f087eec-8bf0-11eb-8dcd-0242ac130003",
"game":
{
"id":null,
"team1":null,
"team2":null,
"gameDate":null,
},
"user":
{
//user fields
}
}
How looks controller:
#GetMapping("/{id}")
public ResponseEntity<GetEventById> getEventById #PathVariable("id") String id) {
GetEventByIResponse dto= service.getEventById(id);
return ResponseEntity
.status(HttpStatus.OK)
.body(dto);
}
In my test i am creating GetEventByIResponse, how it looks:
public class Event {
private String id;
private Game game;
...
}
The JsonPath assert works as follows:
It first parses the path result into a Map/List/Object, to see if the origin was an array/object/simple type.
Then, if it's a Map (like in your case) it tries to parse the path result into the same type as the expected object.
Finally it compare the created object to the expected object using equals().
In your case I see several problems:
The AssertionError talks about an Event although you seem to hand in a Game.
We do not know if the serialization/deserialization works at all
Maybe you should try one of the following:
Place a breakpoint right here to watch the assert steps in your debugger:
Start with simple type comparisons:
.andExpect(jsonPath("$.id").value("foo"))
.andExpect(jsonPath("$.game.id").value("bar"))
Seems like the json path referencing the game child object is incorrect, try below:
.andExpect(jsonPath("$.event.game").value(event.getGame()))

Spring boot 2.1.0 security change with kotlin data class?

This problem make me physically ill.
Joke aside, I've been trying to add an authentication layer to my web app using spring-boot with security plugin. Here is my data class.
#Document(collection = "user")
data class User (
var name : String,
var password : String,
var email : String,
var type : String,
var status : String,
var balance : Int
){
#Id val id : String = ObjectId.get().toHexString()
}
After some searching, Ctr+C, Ctr+V, I'm successfully set-up some custom authentication that will get user information from database, look like this:
override fun loadUserByUsername(name : String): UserDetails {
logger.info(name)
val user = repo.findByName(name)
return User(user!!.name,passwordEncoder.encode(user.password),AuthorityUtils.NO_AUTHORITIES)
}
Here where the fun begin, its seem that the code never run pass val user = repo.findByName(name). Worst thing is, there are no exception being thrown, the code run to that line and the rest just disappear.
Out of frustration, I decide to fake the return object so that I can get pass the authentication like this:
override fun loadUserByUsername(name : String): UserDetails {
logger.info(name)
//val user = repo.findByName(name)
logger.debug("asdkfhasdklfjhasdf")
return User("string",passwordEncoder.encode("you"),AuthorityUtils.NO_AUTHORITIES)
}
Now, finally I can get some exception:
{
"timestamp": "2018-11-08T18:08:29.541+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No accessor to set property #org.springframework.data.annotation.Id()private final java.lang.String com.sonnbh.jwt.User.id!",
"path": "/user"
}
The exception state that spring cannot access property id so I change the type of id from val to var.
#Document(collection = "user")
data class User (
var name : String,
var password : String,
var email : String,
var type : String,
var status : String,
var balance : Int
){
#Id var id : String = ObjectId.get().toHexString()
}
Finally, my app work as expected. However, after some attempt trying to dig deeper to the problem, I found that this problem only occur to spring-boot v2.1.0. My old project which use spring-boot v2.0.5 actually run fine with val id. This led me to some question:
Did I my old implement of data class User properly? I just want to prevent any change to User.id after its being read from database or init. What can I do to improve?
Why spring-boot v2.1 can't access to the property like spring-boot v2.0.5 did?
Spring Data in 2.1. has changed the way in which it deals with final fields in entities. It no longer uses reflection to override the immutability of the fields, which in general is good. There are a few ways to cope with the problem.
They are described here: https://jira.spring.io/browse/DATACMNS-1374?focusedCommentId=182289&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-182289
Here's what the Spring guys recommend:
Add a #PersistenceConstructor to construct the entity that sets immutable fields.
Add wither methods (MyEntity withXxx(…)) to create a new instance that contains the changed property value.
Alternatively: Use Kotlin's data class feature. This will basically do the same as wither methods.
Can only answer the first part; you could try moving the declaration of ID to be apart of the constructor? That will satisfy your requirement of only initialising when the object is created and it will still be read only.

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) }
)
}

GraphQL Java: Using #Batched DataFetcher

I know how to retrieve a bean from a service in a datafetcher:
public class MyDataFetcher implements DataFetcher {
...
#Override
public Object get(DataFetchingEnvironment environment) {
return myService.getData();
}
}
But schemas with nested lists should use a BatchedExecutionStrategy and create batched DataFetchers with get() methods annotated #Batched (see graphql-java doc).
But where do I put my getData() call then?
///// Where to put this code?
List list = myService.getData();
/////
public class MyDataFetcher implements DataFetcher {
#Batched
public Object get(DataFetchingEnvironment environment) {
return list.get(environment.getIndex()); // where to get the index?
}
}
WARNING: The original BatchedExecutionStrategy has been deprecated and will get removed. The current preferred solution is the Data Loader library. Also, the entire execution engine is getting replaced in the future, and the new one will again support batching "natively". You can already use the new engine and the new BatchedExecutionStrategy (both in nextgen packages) but they have limited support for instrumentations. The answer below applies equally to both the legacy and the nextgen execution engine.
Look at it like this. Normal DataFetcherss receive a single object as source (DataFetchingEnvironment#getSource) and return a single object as a result. For example, if you had a query like:
{
user (name: "John") {
company {
revenue
}
}
Your company resolver (fetcher) would get a User object as source, and would be expected to somehow return a Company based on that e.g.
User owner = (User) environment.getSource();
Company company = companyService.findByOwner(owner);
return company;
Now, in the exact same scenario, if your DataFetcher was batched, and you used BatchedExecutionStrategy, instead of receiving a User and returning a Company, you'd receive a List<User> and would return a List<Company> instead.
E.g.
List<User> owners = (List<User>) environment.getSource();
List<Company> companies = companyService.findByOwners(owners);
return companies;
Notice that this means your underlying logic must have a way to fetch multiple things at once, otherwise it wouldn't be batched. So your myService.getData call would need to change, unless it can already fetch data for multiple source object in one go.
Also notice that batched resolution makes sense in nested queries only, as the top level resolver can already fetch a list of object, without the need for batching.

Resources