My requirement is as follows.
Get ApptReq object which will have apptId. Get Appt object from DB and update Appt object with the data from ApptReq and update the table.
Mono<User> monoUser = retrieveUser();
public Mono<ServerResponse> updateAppt(ServerRequest request) {
return apptRepository.findById(request.bodyToMono(ApptReq.class).map(ApptReq::getApptId)).flatMap(appt -> {
return updateAppt(appt, request.bodyToMono(ApptReq.class)).flatMap(apptRepository::save).flatMap(
res -> ServerResponse.created(URI.create(String.format(APPT_URI_FORMAT, res.getApptId()))).build());
});
}
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.map(req -> {
appt.setNotes(req.getNotes());
return monoUser.map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Here getting error in updateAppt method that
can not convert from Mono<Object> to Mono<Appt>.
Is there any better approach?
You've got it almost. I changed nothing in your updateAppt(ServerRequest request) method but made just a slight adjustment in your updateAppt(Appt appt, Mono<ApptReq> apptReq) method, as follows:
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.flatMap(req -> {
appt.setNotes(req.getNotes());
return retrieveUser().map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Watch out for the apptReq.flatMap instead of your apptReq.map and everything works fine. Give it a try!
Reminder: Be careful with nested Monos in other Monos or more generally said nested Publishers.
Related
We obtain a list of data from a SQL Server database. I want to return the list when there is data, but when there is not, I want to return a "No Content" status. My code:
public class Main {
public static void main(String[] args) {
var main = new Main();
var result = main.controllerMethod();
System.out.println("Result: " + result.blockingGet());
}
public Flowable<Person> personList(){
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if(list.isEmpty()) return Flowable.empty();
else return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod(){
var httpStatus =
new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList()
.switchIfEmpty(subs -> Completable.fromAction(() ->
httpStatus.set(HttpStatus.NO_CONTENT)).toFlowable()))
.map(person -> ResponseEntity.status(httpStatus.get())
.body(person));
}
}
result:
Result: <200 OK OK,io.reactivex.rxjava3.internal.operators.flowable.FlowableSwitchIfEmpty#35f983a6,[]>
It seems that you expect httpStatus.set(HttpStatus.NO_CONTENT) to be run, and after running, the status can be read with the correct statuscode. I'm not very familiar with reactive, but by looking at Completable.java I don't think the Action that you provide in Completable::fromAction is run right away.
There is probably a more reactive-like way to solve the problem, but I can't help you there. Instead, here's a solution that might work (or at least get you going in the right direction). Just set the value of the status explicitly yourself before returning your Flowable:
public Flowable<Person> personList(AtomicReference<HttpStatus> reference) {
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if (list.isEmpty()) {
reference.set(HttpStatus.NOT_FOUND);
return Flowable.empty();
}
return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod() {
var httpStatus = new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList(httpStatus))
.map(person -> ResponseEntity.status(httpStatus.get()))
.body(person));
}
Below is the process I want to achieve in through my code.
When I receive JsonRecord I need to check if it exists in Redis Cache.
I) If record is NOT present:
1. Create a record in external system DB using REST call.
2. Create Cache Record from JsonRecord and response from step1
3. Create the external System using response from step2
II) If record is present:
1. Update a record in external system DB using REST call.
2. Update the Cache Record from JsonRecord and response from step 1
3. Update the external System using response from step2
NOTE 1: Calls to external systems in step 1 and step 3 are different.
Issue1: When transform() method in MessageTransformationHandler returns null, it directly going back to the switchIfEmpty block in RecordHandler.
Expected: When transform() method returns null, it should go back to calling method.
Issue2: When transform method in MessageTransformationHandler returns null, it directly going back to the switchIfEmpty block in RecordHandler.
Expected: When transform method returns null, it should go back to calling method. then from that method to flatMap condition in RecordHandler class.
#Component
public class RecordHandler {
#Autowired
private contentService contentService;
#Autowired
private RedisService redisService;
#Autowired
private MappingService mappingService;
public Mono<String> handleMessage(JsonRecord record, String recordId, String , long startTime) {
try {
return redisService.getObjectFromRedis(record.getRecordId())
.flatMap(redisResponse -> {
return contentService.updateEvent(record, redisResponse)
.flatMap(dBResponse -> mappingService.createOrUpdateMappingData(dBResponse, record, redisResponse))
.filter(mappingResponse -> mappingResponse!=null)
.flatMap(mappingResponse -> contentService.storeOrUpdatePrices(record, mappingResponse, requestId));
}
}).switchIfEmpty(Mono.defer(() -> {
return contentService.createEvent(record, redisResponse)
.flatMap(dBResponse -> mappingService.createOrUpdateMappingData(dBResponse, record , startTime))
.filter(mappingResponse -> mappingResponse!=null)
.flatMap(mappingResponse -> contentService.storeOrUpdatePrices(record, mappingResponse, requestId));
}));
} catch (Exception e) {
return Mono.error(e);
}
}
}
#Service
public class ContentServiceImpl implements ContentService {
#Override
public Mono<EventType> updateEvent(JsonRecord record, EventsMapping mappingData) {
return Mono.just(record).flatMap(record -> {
MessageTransformationHandler messageHandler = messageTransformationHandlerFactory
.getHelper(record.getClassName());
//transform method can return null value
ContentManagementModelGeneric payload = messageHandler.transform(record, mappingData);
if (payload == null) {
return Mono.empty();
}
return ContentRESTService.createRecord(record, payload);
}).flatMap(sportsDBResponse -> Mono.just(getResponseEvent(record, sportsDBResponse)));
}
}
public class MessageTransformationHandler {
public ContentModel transform(JsonRecord record, MappingData mappingData) {
ContentModel cm = null;
List<MarketType> somethingList = record.getSomething().getSomeList_().stream().map(something -> {
//do something and return null.
return null
})
.filter(something -> something != null)
.collect(Collectors.toList());
if(somethingList == null || somethingList.isEmpty()) {
return null;
}
return cm;
}
}
I try to combine CompletionStages in play framework and then return a Result like ok(). This is my setup:
AccountDao which has two methods:
public CompletionStage<Account> getUserByEmail(String email) {
return supplyAsync(() -> ebeanServer.find(Account.class).setUseCache(true).where().eq(EMAIL, email).findOne(), executionContext).thenApply(account -> {
return account;
});
}
public CompletionStage<Void> updateAccount(Account account) throws OptimisticLockException{
return runAsync(() -> {
ebeanServer.update(account);
}, executionContext);
}
And then i have my controller with the action:
public CompletionStage<Result> editAccount() {
Map<String, String[]> form_values = request().body().asFormUrlEncoded();
return CompletableFuture.completedFuture(ok());
}
So now in the action i want first to execute getUserByEmail and then i want to set some values and Update this with updateAccount method. How can i combine this two stages without blocking play context? I tried different setups with thenCompose and combine but i dont get it ...
Here one of my tries:
public CompletionStage<Result> editAccount() {
Map<String, String[]> form_values = request().body().asFormUrlEncoded();
accountDao.getUserByEmail(session().get("accountEmail")).thenCompose(x -> accountDao.updateAccount(x).thenApplyAsync(account -> {
return ok("Going to save account edits");
}, httpExecutionContext.current()));
return CompletableFuture.completedFuture(ok("Fehler am Ende"));
}
The problem here is, that i cannot access the account (x) from before because i cannot set this as function ... like this:
public CompletionStage<Result> editAccount() {
Map<String, String[]> form_values = request().body().asFormUrlEncoded();
accountDao.getUserByEmail(session().get("accountEmail")).thenCompose(x -> {
//Update vars in x and then save to database
accountDao.updateAccount(x);
}.thenApplyAsync(account -> {
return ok("Going to save account edits");
}, httpExecutionContext.current()));
return CompletableFuture.completedFuture(ok("Fehler am Ende"));
}
Here i get the error: The target type of this expression must be a functional interface and plays says that i have to include the return statement at the end of the function!
I just dont get it ... Thanks for your help!
#Marimuthu Madasamy Thats no exactly what i want. In your awnser i would update the account twice. On etime in accountDao.updateAccount(account) and in accountDao.saveAccount(account); I want something like this:
return accountDao.getUserByEmail("mail").thenCompose(account -> {
account.setName("NewName");
accountDao.save(account);
} .thenApplyAsync(voidInput -> {
return ok("Account saved");
}, httpExecutionContext.current()));
In this case in only update the account once and only return the result on the httpExecutionContext
If I understand your question correctly, you want to access (to save?) account after updateAccount(account) method call.
Since updateAccount method returns CompletionStage<Void>, when you call thenApplyAsync on this stage, the input type would only be Void which is not Account. But with the following code, you would still have access to the account returned from getUserByEmail assuming updateAccount mutates the account by your text "update vars in x":
public CompletionStage<Result> editAccount() {
return accountDao
.getUserByEmail(email)
.thenCompose(account -> accountDao.updateAccount(account)
.thenApplyAsync(voidInput -> {
// here you still have access to the `account` from `getUserByEmail` method
accountDao.saveAccount(account);
return ok("Account saved");
}, httpExecutionContext.current());
}
Ok i found my own awnser here with the support of Marimuthu Madasamy! Thanks. I trie to explain it. First here is the code:
public CompletionStage<Result> editAccount() {
Map<String, String[]> form_values = request().body().asFormUrlEncoded();
return accountDao.getUserByEmail(session().get("accountEmail")).thenApply(account -> {
System.out.println("Async get Account / "+Thread.currentThread());
account.setCompany(form_values.get("company")[0]);
return accountDao.updateAccount(account);
}).thenApplyAsync(account -> {
System.out.println("Async resutl / "+Thread.currentThread());
return ok("Account saved normal");
}, httpExecutionContext.current()).exceptionally(e ->{
System.out.println("Async exception / "+Thread.currentThread());
System.out.println(e.getLocalizedMessage());
return ok(e.getLocalizedMessage());
});
}
Ok at first i execute accountDao.getUserByEmail() as you can see at top in my awnser this returns CompletionStage and is executed in my database execution context. At next with thenApply i get the result and i execute the next Async mehtod. I use thenApply instand of thenApplyAsync so the next call is also executed with the database execution context without setting it explicitly. After the accountDao.updateAccount() i execute the next stage on the httpExecutionContext to replay a result or to quit exceptionally! I really hope it is clear and helps some one!
i'm having a problem with rx java.
I have a current stream that in some point gives to me an Either
That response has external resources, like image urls, and i want to send each url to an external class, download it asyncronously, and if all of them are ok, continue with that either received or if one of that resources fails while is being downloaded return an Either.error(MyError());
My problem is that as i'm creating a new observable inside the resources provider, it needs to be subscribed to start run, but i do not know how can i do.
This is my current code (not sure if compiles but you get the idea):
private Observable<Either<Error, Response>> prefetchResourcesOrError(final Either<Error, Response> errorOrResponse) {
if (errorOrResponse.isResponseWithImages()) {
ResponseImages responseImages = (ResponseImages) responseImages.getResponse();
return
Observable.fromIterable(responseImages.getResources()
.map(resourcesProvider::prefetch)
.onErrorReturn(throwable -> Observable.<Either<Error, Response>>just(Either.left(new MyError())))
.map(observable -> errorOrResponse);
} else {
return Observable.just(errorOrResponse);
}
}
//Resource prefetch method
Observable prefetch(Resource resource) {
return Observable.just(resource)
.flatMap((Function<Resource, ObservableSource<?>>) res1 ->
Observable.create((ObservableOnSubscribe<Void>) emitter ->
resourceLoader.prefetch(res1.getUrl(), new ImageLoaderListenerAdapter() {
#Override
public void onException(Exception e) {
emitter.onError(e);
}
#Override
public void onResourceReady() {
emitter.onNext(null);
}
})
)
);
}
}
//The main Stream
//MainObservable is an Either<Error, Response> errorOrResponse
return mainObservable.flatMap(this::prefetchResourcesOrError);
I have two observable executions.
I want to execute the second only if first one is empty/null and when finish to execute the final code block.
However - the second observable always executed even if first observable is not empty.
handleLocation(msg).filter(result -> result != null).switchIfEmpty(addLocation(msg)).subscribe(
response -> {
handleResponse(routingContext, transactionId, msg, response);
});
private Observable<LocationDTO> handleLocation(JsonObject msg) {
Location locationDTO=new locationDTO();
...
return Observable.just(locationDTO);
}
as you see handleLocation will never return null/empty object.
why addLocation(msg) getting triggered?
addLocation signature:
private Observable<MyDTO> addLocation(JsonObject msg) {
return redisRepo.getLocationByIp(ip).switchIfEmpty(getLocationByHost(host);
}
private Observable<LocationDTO> getLocationByHost(Strin host) {
...
return Observable.just(new LocationDTO());
I managed to fix this by adding return Observable.fromCallable(() to addLocation. any idea why it resolved this way?
Using filter will emit all the values that pass the filter. If I understand right, you are looking for something like:
Observable.concat(cache, remote)
.first(new Func1<Result, Boolean>() {
#Override
public Boolean call(Result result) {
return result != null;
}
});
This will emit the first non-null "Result".