I have a WebFlux handler as below.
#Transactional
public Mono<ServerResponse> submitOrder(final ServerRequest request) {
return context.retrieveUser().flatMap(usr -> {
try {
return Mono.zip(branchSetting, labOrderDetail, labOrderTests).flatMap(response -> {
final Mono<String> submitOrderMono = service.submitOrder(usr);
final Mono<Integer> updateStatusMono = orderRepository.updateStatus(orderId);
return Mono.zip(submitOrderMono, updateStatusMono).flatMap(submitResponse -> {
return ok().bodyValue(submitResponse.getT1());
}).onErrorResume(e -> {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
});
});
} catch (Throwable e) {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
}
});
}
submitOrder method from service class,
public Mono<String> submitOrder(final Order order,
if (order.getPatientId() != null) {
throw new ServiceException("Missing Id for patient !!!", HttpStatus.BAD_REQUEST.value());
}
}
Here, I am doing some validation and throwing Exception.
But, this exception is not getting into onErrorResume or catch block in the calling main method and hence the service caller sees 500 error.
Not sure what is wrong here.
When working in a reactive WebFlux context, throwing exceptions and using try-catch-block is, imho, not really best practice.
The more idiomatic approach would be to use Mono.error() instead of throw commands. Mono.error() emits an error signal so that a subsequent onErrorResume() could deal with it.
That being said, submitOrder() could look like this:
public Mono<String> submitOrder(final Order order) {
if (order.getPatientId() == null) {
Mono.error(new ServiceException("Missing Id for patient !!!", 500));
}
return Mono.just("some reasonable result");
}
With this rewrite, the first snippet should (maybe with some minor adjustments) work this way:
public Mono<ServerResponse> submitOrder(final ServerRequest request) {
return context.retrieveUser().flatMap(usr -> {
return Mono.zip(branchSetting, labOrderDetail, labOrderTests).flatMap(response -> {
final Mono<String> submitOrderMono = service.submitOrder(usr);
final Mono<Integer> updateStatusMono = orderRepository.updateStatus(orderId);
return Mono.zip(submitOrderMono, updateStatusMono).flatMap(submitResponse -> {
return ok().bodyValue(submitResponse.getT1());
}).onErrorResume(e -> {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
});
});
});
}
Related
I have a Spring webflux app with the below method.
#Override
public Mono<Integer> updateSetting(int orgId, IntegrationDto dto,
Map<String, Object> jsonMap) {
return retrieveServices(dto.getClientId()).flatMap(services -> {
jsonMap.put("service", services);
return categoryRepository.findCategoryIdCountByName("test", orgId)
.flatMap(categoryIdCount -> {
final ServiceDto serviceInput = new ServiceDto();
if (categoryIdCount == 0) {
return inventoryCategoryRepository.save(InventoryCategory.of("test", orgId))
.flatMap(category -> {
return saveServices(serviceInput, orgId, jsonMap,
category.getCategoryId());
});
} else {
// Some Logc here ...
}
});
}).onErrorResume(e -> {
if (e instanceof WebClientResponseException) {
int statusCode = ((WebClientResponseException) e).getRawStatusCode();
throw new LabServiceException("Unable to connect to the service !", statusCode);
}
throw new ServiceException("Error connecting to the service !");
});
}
private Mono<Services> retrieveServices(final String clientId) {
return webClient.get().uri(props.getBaseUrl() + "/api/v1/services")
.retrieve().bodyToMono(Services.class);
}
private Mono<Integer> saveInventories(ServiceInput serviceInput, int orgId, Map<String, Object> jsonMap,
Long categoryId) {
return refreshInventories(serviceInput, orgId, categoryId).flatMap(reponse -> {
return updateSetting(branchId, jsonMap);
});
}
private Mono<Integer> refreshInventories(ServiceInput serviceInput, int orgId, Long categoryId) {
return inventoryRepository.findAllCodesByTypeBranchId(branchId).collectList().flatMap(codes -> {
return retrieveAvailableServices(Optional.of(serviceInput), categoryId).flatMap(services -> {
List<Inventory> inventories = services.stream()
.filter(inventory -> !codes.contains(inventory.getCode()))
.map(inventoryDto -> toInventory(inventoryDto, branchId)).collect(Collectors.toList());
if (inventories.size() > 0) {
return saveAllInventories(inventories).flatMap(response -> {
return Mono.just(orgId);
});
} else {
return Mono.just(orgId);
}
});
});
}
Here, the updateSettig public method is being invoked from a REST call and all gets executed as expected.
Now, I want to execute the same with a different flow as well like a scheduler.
When I invoke from a scheduler also, It works.
updateSetting(orgId, dto, jsonMap).subscribe();
But, I want to wait until the updateSetting gets executed.
So, tried with the code below.
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
});
With the above code, updateSetting method gets invoked, but not getting into the retrieveServices.
return retrieveServices(dto.getClientId()).flatMap(services -> {
You always need to subscribe in the end. So your code should be:
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
}).subscribe();
I'cand figure out how to close websocket on server side.
Here is my code:
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> flux = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(s -> {
if (s.equals("bye")) {
//todo: NEED TO CLOSE - session.close()
return "You said bye!";
} else {
return s;
}
})
.map(String::toUpperCase)
.map(session::textMessage).log();
return session.send(flux).log();
}
You can use takeUntil method:
Flux<WebSocketMessage> flux = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.takeUntil("bye"::equals)
.map(s -> {
if (s.equals("bye")) {
return "You said bye!";
} else {
return s;
}
})
.map(session::textMessage).log();
I have written an #Aspect to intercept Reactive Methods that return values in Mono/Flux. Using #AfterReturning advice, i'm trying to fire an APNS notification by calling a webservice.
unfortunately the processNotification Mono services is immediately returning onComplete signal without executing the chain of calls. Below is my sample program.
#Aspect
#Component
#Slf4j
public class NotifyAspect{
private final NotificationServiceHelper notificationServiceHelper;
#Autowired
public NotifyAspect(NotificationServiceHelper notificationServiceHelper) {
this.notificationServiceHelper = notificationServiceHelper;
}
#AfterReturning(pointcut = "#annotation(com.cupid9.api.common.annotations.Notify)", returning = "returnValue")
public void generateNotification(JoinPoint joinPoint, Object returnValue) throws Throwable {
log.info("AfterReturning Advice - Intercepting Method : {}", joinPoint.getSignature().getName());
//Get Intercepted method details.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//Get the Notification Details.
Notify myNotify = method.getAnnotation(Notify.class);
if (Mono.class.isAssignableFrom(returnValue.getClass())) {
Mono<Object> result = (Mono<Object>) returnValue;
result.doOnSubscribe(o -> {
log.debug("On Subscription...");
notificationServiceHelper.processNotification(myNotify.notificationType())
.doOnError(throwable -> {
log.error("Exception in notification processor",throwable);
});
});
}
}
}
#Slf4j
#Service
public class NotificationServiceHelper {
private ReactiveUserProfileRepository userProfileRepository;
#Value("${services.notification.url}")
private String notificationServiceUrl;
private RestWebClient restWebClient;
#Autowired
public NotificationServiceHelper(RestWebClient restWebClient,
ReactiveUserProfileRepository reactiveUserProfileRepository) {
this.restWebClient = restWebClient;
this.userProfileRepository = reactiveUserProfileRepository;
}
public Flux<Notification> processNotification(NotificationSchema.NotificationType notificationType) {
/*Get user profile details*/
return SessionHelper.getProfileId()
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 1!")))
.flatMap(profileId ->
Mono.zip(userProfileRepository.findByIdAndStatus(profileId, Status.Active), SessionHelper.getJwtToken()))
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 2!")))
.flatMapMany(tuple2 ->{
//Get user details and make sure there are some valid devices associated.
var userProfileSchema = tuple2.getT1();
log.info("Processing Notifications for User Profile : {}", userProfileSchema.getId());
if (Objects.isNull(userProfileSchema.getDevices()) || (userProfileSchema.getDevices().size() < 1)) {
return Flux.error(new InternalServerError("No Devices associate with this user. Can not send notifications."));
}
//Build Notification message from the Notification Type
var notificationsMap = new LinkedHashSet<Notification>();
userProfileSchema.getDevices().forEach(device -> {
var notificationPayload = Notification.builder()
.notificationType(notificationType)
.receiverDevice(device)
.receiverProfileRef(userProfileSchema.getId())
.build();
notificationsMap.add(notificationPayload);
});
//Get session token for authorization
var jwtToken = tuple2.getT2();
//Build the URI needed to make the rest call.
var uri = UriComponentsBuilder.fromUriString(notificationServiceUrl).build().toUri();
log.info("URI built String : {}", uri.toString());
//Build the Headers needed to make the rest call.
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.AUTHORIZATION, jwtToken);
var publishers = new ArrayList<Mono<ClientResponse>>();
notificationsMap.forEach(notification -> {
publishers.add(restWebClient.post(uri, headers, notification));
});
return Flux.merge(publishers).flatMap(clientResponse -> {
var httpStatus = clientResponse.statusCode();
log.info("NotificationService HTTP status code : {}", httpStatus.value());
if (httpStatus.is2xxSuccessful()) {
log.info("Successfully received response from Notification Service...");
return clientResponse.bodyToMono(Notification.class);
} else {
// return Flux.empty();
return clientResponse.bodyToMono(Error.class)
.flatMap(error -> {
log.error("Error calling Notification Service :{}", httpStatus.getReasonPhrase());
if (httpStatus.value() == 400) {
return Mono.error(new BadRequest(error.getMessage()));
}
return Mono.error(new InternalServerError(String.format("Error calling Notification Service : %s", error.getMessage())));
});
}
});
}).doOnError(throwable -> {
throw new InternalServerError(throwable.getMessage(), throwable);
});
}
}
How can we trigger this call in async without making the interception wait.. right now processNotification is always returning onComplete signal without executing. The chain is not executing as expected
#Target({ElementType.PARAMETER, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Log {
public String title() default "";
}
#SuppressWarnings({"unchecked"})
#Around("#annotation(operlog)")
public Mono<Result> doAround(ProceedingJoinPoint joinPoint, Log operlog) {
Mono<Result> mono;
try {
mono = (Mono<Result>) joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
return mono.doOnNext(result -> {
//doSomething(result);
};
}
We are working with project reactor and having a huge problem right now. This is how we produce (publish our data):
public Flux<String> getAllFlux() {
return Flux.<String>create(sink -> {
new Thread(){
public void run(){
Iterator<Cache.Entry<String, MyObject>> iterator = getAllIterator();
ObjectMapper mapper = new ObjectMapper();
while(iterator.hasNext()) {
try {
sink.next(mapper.writeValueAsString(iterator.next().getValue()));
} catch (IOException e) {
e.printStackTrace();
}
}
sink.complete();
}
} .start();
});
}
As you can see we are taking data from an iterator and are publishing each item in that iterator as a json string. Our subscriber does the following:
flux.subscribe(new Subscriber<String>() {
private Subscription s;
int amount = 1; // the amount of received flux payload at a time
int onNextAmount;
String completeItem="";
ObjectMapper mapper = new ObjectMapper();
#Override
public void onSubscribe(Subscription s) {
System.out.println("subscribe");
this.s = s;
this.s.request(amount);
}
#Override
public void onNext(String item) {
MyObject myObject = null;
try {
System.out.println(item);
myObject = mapper.readValue(completeItem, MyObject.class);
System.out.println(myObject.toString());
} catch (IOException e) {
System.out.println(item);
System.out.println("failed: " + e.getLocalizedMessage());
}
onNextAmount++;
if (onNextAmount % amount == 0) {
this.s.request(amount);
}
}
#Override
public void onError(Throwable t) {
System.out.println(t.getLocalizedMessage())
}
#Override
public void onComplete() {
System.out.println("completed");
});
}
As you can see we are simply printing the String item which we receive and parsing it into an object using jackson wrapper. The problem we got now is that for most of our items everything works fine:
{"itemId": "someId", "itemDesc", "some description"}
But for some items the String is cut off like this for example:
{"itemId": "some"
And the next item after that would be
"Id", "itemDesc", "some description"}
There is no pattern for those cuts. It is completely random and it is different everytime we run that code. Ofcourse our jackson is gettin an error Unexpected end of Input with that behaviour.
So what is causing such a behaviour and how can we solve it?
Solution:
Send the Object inside the flux instead of the String:
public Flux<ItemIgnite> getAllFlux() {
return Flux.create(sink -> {
new Thread(){
public void run(){
Iterator<Cache.Entry<String, ItemIgnite>> iterator = getAllIterator();
while(iterator.hasNext()) {
sink.next(iterator.next().getValue());
}
}
} .start();
});
}
and use the following produces type:
#RequestMapping(value="/allFlux", method=RequestMethod.GET, produces="application/stream+json")
The key here is to use stream+json and not only json.
here is my code :
// Observable from RxView
RxView.clicks(mBtnLogin)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
#Override
public void call(Void aVoid) {
String userName = mEditUserName.getText().toString();
String passWord = mEditPassWord.getText().toString();
if (TextUtils.isEmpty(userName)) {
Toast.makeText(LoginActivity.this, R.string.input_user_name, Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(passWord)) {
Toast.makeText(LoginActivity.this, R.string.input_pass_word, Toast.LENGTH_SHORT).show();
return;
}
LoginAction action = Constants.retrofit().create(LoginAction.class);
// Observable from Retrofit
Observable<String> call = action.login(userName, MD5.encode(passWord));
call.subscribeOn(Schedulers.io())
.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
System.out.println("completed");
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(String s) {
System.out.println("next" + s);
}
});
}
});
Is there any way you could combine the Observable from RxView and the Observable from retrofit ?
i think the code is ugly and Do not meet the ReactiveX's specifications.
Yes, you would use the .flatMap() operator:
RxView.clicks(mButton)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Void, Observable<Response>>() {
#Override
public Observable<Response> call(Void aVoid) {
return apiService.getResponse().subscribeOn(Schedulers.io());
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
would look a bit better with lambdas:
RxView.clicks(mButton)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.flatMap(aVoid -> apiService.getResponse().subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();