I have a web service with a couple of endpoints, one of them is a PUT endpoint that receives an XML and adds some information to a Redis server.
My endpoint is mapped in a configuration class:
@Configuration
public class RouterConfig {
@Autowired
private YAMLConfig yamlConfig;
@Bean
public RouterFunction<ServerResponse> getRoutes(final ServiceHandler serviceHandler) {
return RouterFunctions.route()
.GET(yamlConfig.getEnvPrefix() + STATUS_ENDPOINT, serviceHandler::getStatus)
.GET(yamlConfig.getEnvPrefix() + STOCK_CAP_API + PRODUCT_CAPPING_ENDPOINT, serviceHandler::getStockCapForProducts)
.PUT(yamlConfig.getEnvPrefix() + STOCK_CAP_API + ADD_PRODUCT_CAP_FOR_CUSTOMER, RequestPredicates.contentType(MediaType.APPLICATION_XML), serviceHandler::AddStockCap)
.build();
}
}
The handler mehtod for the PUT endpoint looks like this:
@NonNull
public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
log.info("Adding new stock caps");
serverRequest.bodyToMono(CustomerCap.class)
.flatMap(stockCapService::addCustomerStockCap)
.doOnEach(result -> System.out.println("Cap added - " + result));
return ServerResponse.ok().build();
}
The bodyToMono
is mapped to a class that represents the XML payload like the one below, and the attribute Customer inside the class is also mapped to a class in the same fashion.
@XmlRootElement(name="customerCap")
@XmlAccessorType(XmlAccessType.FIELD)
public class CustomerCap {
@XmlElement
private List<Customer> customer;
public List<Customer> getCustomer() {
return customer;
}
public void setCustomer(List<Customer> customer) {
this.customer = customer;
}
}
The implementation for the addCustomerStockCap is
@Override
public Mono<Boolean> addCustomerStockCap(CustomerCap customerCap) {
log.info("Starting adding process");
customerCap.getCustomer().forEach(customer -> {
final var customerUID = customer.getCustomerNumber();
customer.getCap().forEach(cap -> {
redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), CAP_LIMIT, String.valueOf(cap.getCustomerCapLimit()));
redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_FROM, String.valueOf(cap.getCapValidFrom()));
redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_TO, String.valueOf(cap.getCapValidTo()));
setExpirationOnHash(customerUID, cap);
});
});
return Mono.just(Boolean.TRUE);
}
The method to handle the endpoint is being invoked properly and I can see the "Addin new stock caps" message on the log, but then the addCustomerStockCap
is never invoked, with no exceptions or errors are being thrown, the method simply doesn’t execute.
Another test that I did was creating a controller instead of a functional endpoint. The controller method ended up like this
@PutMapping(STOCK_CAP_API + ADD_PRODUCT_CAP_FOR_CUSTOMER)
@ResponseStatus(HttpStatus.OK)
public void AddStockCap(@RequestBody CustomerCap customerCap) {
log.info("Adding new stock caps");;
stockCapService.addCustomerStockCap(customerCap);
}
When I did this I got some errors on the XML parse that I fixed only adding the annotation @NoArgsConstructor
to the CustomerCap
and other element classes. After this fix, the controller/endpoint worked as expected.
The problem still is that I need to make it work on the functional endpoint, we have several applications and we use functional endpoints as our standard so leaving only this endpoint like this wouldn’t make sense.
2
Answers
The only way that worked for me to make sure the method is invoked was to add its result as the response body.
The problematic method ended up like this:
And the handler like this
Not ideal as the goal was for this endpoint to only return a 200 code with no body, but it's a workaround until a found another way to invoke it.
Also on spring documentation, for XML and JSON payload we should use Flux instead of Mono
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-fn-handler-functions
The issue is that your
Mono
is never subscribed and thus nothing is emitted and as a consequencestockCapService::addCustomerStockCap
is never invoked. The following should work:Or
Update 21/11/2021
Try the following. You don’t really need
addCustomerStockCap
to return aMono<Boolean>
:And now you can somewhat simplify your
AddStockCap
(flatMap
turns amap
):