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();
Related
Is there is a way to throw the exception to parent method in webclient. I am not able to throw the exception to parent method getStores(). What is the way to throw the exception to parent method in below code.
For example:
#Override
public Mono<Stores> getStores(String id) {
Mono<Stores> stores = null;
try {
WebClientRequest<Stores> webClientRequest = new WebClientRequest<>();
webClientRequest.setContentType("application/json");
webClientRequest.setEndPoint("http://localhost:8090/api/stores/eee");
webClientRequest.setPathParam(id);
stores= webClient.getAsynchronousWebClient(webClientRequest, Stores.class);
} catch(MtampWebClientException e) {
System.out.println("Hello");
}
return stores;
}
public <T> Mono<K> getAsynchronousWebClient(WebClientRequest<T> webClientRequest, Class<K> clazz) {
ResponseSpec retrieve = performWebRequest(webClientRequest);
return retrieve.bodyToMono(clazz).doOnNext(response -> webClientResponse(webClientRequest, response));
}
private <T> ResponseSpec performWebRequest(WebClientRequest<T> webClientRequest) {
WebClient client = webClientBuilder();
LinkedMultiValueMap<String, String> map = setHeaders(webClientRequest);
Consumer<HttpHeaders> consumer = it -> it.addAll(map);
ResponseSpec retrieve;
if (null != webClientRequest.getPathParam()) {
retrieve = client.get().uri(webClientRequest.getEndPoint() + "/" + webClientRequest.getPathParam())
.headers(consumer).retrieve();
} else if (null != webClientRequest.getQueryParam()) {
LinkedMultiValueMap<String, String> queryMap = new LinkedMultiValueMap<>();
webClientRequest.getQueryParam().entrySet().stream().forEach(e -> queryMap.add(e.getKey(), e.getValue()));
retrieve = client.get()
.uri(uriBuilder -> uriBuilder.path(webClientRequest.getEndPoint()).queryParams(queryMap).build())
.headers(consumer).retrieve();
} else {
retrieve = client.get().uri(webClientRequest.getEndPoint()).headers(consumer).retrieve();
}
return retrieve;
}
private WebClient webClientBuilder() {
HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
return WebClient.builder().filter(ExchangeFilterFunction.ofResponseProcessor(this::errorHandler))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
public Mono<ClientResponse> errorHandler(ClientResponse clientResponse) {
if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else {
return Mono.just(clientResponse);
}
}
I want to return Mono of Boolean from the below method but getting compilation error with flatMap().
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return Mono.just(customRefFlux.collectList().flatMap(customRefList -> {
customRefList.stream().anyMatch(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}));
}
It is internally calling:
public Flux<CustomReference> getCustomsReferenceRule() {
CustomReference c1 = new CustomReference();
c1.setCrKey("ECU");
c1.setType("MRN");
CustomReference c2 = new CustomReference();
c2.setCrKey("FUE");
c2.setType("FUE");
Flux<CustomReference> custRefFlux = Flux.just(c1, c2);
return custRefFlux;
}
The POJO class
#Data
public class CustomReference {
private String type;
private String crKey;
}
You likely don't want to mix streams and collections unless needed. If I'm following this should be a simple:
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.any(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.collectList().flatMap(customRefList -> {
List<CustomReference> l = customRefList.stream()
.filter(customRef -> customRef.getType().equalsIgnoreCase(referenceType))
.collect(Collectors.toList());
if (!l.isEmpty())
return Mono.just(Boolean.TRUE);
else
return Mono.just(Boolean.FALSE);
});
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());
}
});
});
});
}
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);
};
}