Spring Webflux with Spring MVC - nulls emitted when using doOnEach - spring-mvc

I'm experimenting a bit with Spring Webflux and Spring MVC, and encountered an interesting case.
Starting with a simple controller:
#GetMapping
public Mono<String> list(final Model model) {
Flux<User> users = this.userRepository.findAll();
model.addAttribute("users", users);
return Mono.just("users/list");
}
The userReposutory is a custom ConcurrentHashMap-based implementation. Here you can find the findAll method:
#Override
public Flux<User> findAll() {
return Flux.fromIterable(this.users.values());
}
Whenever I try to return to access the "users/list" view, everything seems to be working properly.
But, if I try to rewrite the controller using an idiomatic reactive approach, problems start appearing:
#GetMapping
public Mono<String> list(final Model model) {
return this.userRepository.findAll()
.collectList()
.doOnEach(users -> model.addAttribute("users", users.get()))
.map(u -> "users/list");
}
If I hit the endpoint, I'm getting this in logs:
java.lang.IllegalArgumentException: ConcurrentModel does not support null attribute value
at org.springframework.util.Assert.notNull(Assert.java:193)
at org.springframework.ui.ConcurrentModel.addAttribute(ConcurrentModel.java:75)
at org.springframework.ui.ConcurrentModel.addAttribute(ConcurrentModel.java:39)
at com.baeldung.lss.web.controller.UserController.lambda$list$0(UserController.java:37)
at reactor.core.publisher.FluxDoOnEach$DoOnEachSubscriber.onError(FluxDoOnEach.java:132)
Apparently, some stray null is making its way there. Let's filter out all of them eagerly then:
#RequestMapping
public Mono<String> list(final Model model) {
return this.userRepository.findAll()
.filter(Objects::nonNull)
.collectList()
.filter(Objects::nonNull)
.doOnEach(users -> model.addAttribute("users", users.get()))
.map(u -> "users/list");
}
Same problem, but... if I squeeze everything in a map() call, everything works again:
#GetMapping
public Mono<String> list(final Model model) {
return this.userRepository.findAll()
.collectList()
.map(users -> {
model.addAttribute("users", users);
return "users/list";
});
}
Although, placing side-effects in map is not optimal.
Any ideas what's wrong with the doOnEach() here?

Very nice question. Let's see what the JavaDocs tell about doOnEach:
public final Mono<T> doOnEach(Consumer<? super Signal<T>>
signalConsumer)
Add behavior triggered when the Mono emits an item,
fails with an error or completes successfully. All these events are
represented as a Signal that is passed to the side-effect callback
Curious. The users in doOnEach(users -> ...) is not an List<User> but a Signal<List<User>>. This Signal<T> object won't be null, which explains why the filter methods in the second version don't work.
The JavaDocs for Signal<T> says that the get() method is explicitly marked as #Nullable and will return a non-null value only on next item arrives. If the completion or error signal is generated, then it will return null.
Solutions:
Use doOnNext instead: You are interested in the next value, not any signal that comes from the source stream.
Do a null-check in doOnEach lambda: This will work too, but since you're not interested in other events, is superfluous.

Related

Security Context is null with CompletableFuture

I have a strange issue.
Given this code:
#RequestMapping(value = {"/randomizer"}, method = RequestMethod.POST)
public CompletableFuture<String> randomizer(){
CompletableFuture<String> someString = stringService
.findRandomByInput("123")
.thenCombine(stringService.findAnotherRandomByInput("321"), (result1, result2) -> {
return applyRandom(result1, result2);
});
CompletableFuture<Void> computation = computingService.computeRandomByInput(RandomDto.empty(), "123");
return someString.thenCombineAsync(computation, (result1, result2) -> {
combineIt(result1, result2, getCurrentApplicationUser());
}, taskExecutor);
}
By calling getCurrentApplicationUser() i´m acessing spring´s SecurityContextHolder.getContext().getAuthentication() interface.
I have this taskExecutor:
#Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(6);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("DmpkApplication-");
executor.initialize();
// the following is necessary because of:
// https://stackoverflow.com/a/57434013/7320372
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
So the problem is:
I call the above randomizer controller like 5 times and by the 6th time, the getAuthentication() call is null.
Sometimes the first call to the controller yields null and all other subsequent calls work.
I don´t know what´s the issue here.
I found a fix myself.
I renamed the above bean
public Executor taskExecutor()
the following:
public Executor executor()
Now the issue is gone. I think Spring defines already an executor itself. The previous taskExecutor was an additional one. But somehow the Spring one executor got called which is not a DelegatingSecurityContextAsyncTaskExecutor by default.
So maybe that is the issue.
Nevermind - now it works!

Around Advice not working when dependent on response from other REST service

I am working with Spring AOP to define a common fallback method instead of duplicating code.
I used #Around as I have to return the object from Aspect.I am trying to decide #Around advice depending on the response returned,but not able to do so.
Here is my controller:
#RequestMapping(value = "/add/employee", method = RequestMethod.GET)
public EmployeeResponse addEmployee(#RequestParam("name") String name, #RequestParam("empId") String empId) {
EmployeeResponse employeeResponse=employeeService.createEmployee(name, empId);
return employeeResponse;
}
createEmployee in the service class is used to call another endpoint to insert some data.I want to decide my advice based on the employeeResponse but not able to do so.
I tried #AfterReturning also,but I can't return the object if I use that.
Below is my aspect class:
#Around(value = "execution(* com.test.service.EmployeeService.*(..)) and args(name,empId)")
public Object getAllAdvice2(ProceedingJoinPoint pjp, String name,String empId) throws Throwable {
System.out.println("Inside Aspect");
Object[] arguments = pjp.getArgs();
if (!checkForPath()) {
return pjp.proceed();
}
System.out.println("Call Second path please!!");
return arguments;
}
private boolean checkForPath() {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getResponse();
return response.getStatus()==501?true:false;
}
}
I did use HttpServletResponse and RequestContextHolder to get the context but seems it will take the present context i.e. "/add/employee".
How can I return the actual status from the checkForPath () (since I don't need to call pjp.proceed for every status code returned) so that I can execute the line System.out.println("Call Second path please!!"); depending on my error code.
Can anyone pls suggest where it is going wrong?
Your aspect code is quite chaotic and does not make much sense:
You are trying to check for a response before calling proceed(), as R.G said. Use something like EmployeeResponse response = (EmployeeResponse) proceed() instead, inspect the response and then decide what to do next.
You already bind the method parameters to name and empId, there is no need to use pjp.getArgs().
return arguments does not make sense because you ought to return an EmployeeResponse object (either the original result or another one), not the array of method arguments.

Return a data object with a BadRequestResult / BadRequestErrorMessageResult

I'd like to return a data object that contains the details of the error with a BadRequestErrorMessageResult or BadRequestErrorMessageResult object like so:
public IHttpActionResult Action(Model model)
{
var validationResult = model.Validate();
if (validationResult.Successful)
{
// this one's okay; it supports sending data with a 200
return Ok(validationResult);
}
else
{
// However, how do I return a custom data object here
// like so?
// No such overload, I wish there was
// return BadRequest(validationResult);
}
}
The only three overloads of the ApiController.BadRequest() method are:
1. BadRequest();
2. BadRequest(string message);
3. BadRequest(ModelStateDictionary modelState);
Even with #3, a model state dictionary is ultimate a deep collection with one layer upon another, at the bottom of which, though, is a bunch of KeyValuePair<string, ModelError> where each ModelError also only has either a string or an Exception object.
Therefore, even with #3, we are only able to pack a string to send and not a custom object like I want to.
I am really not asking how I may go about working a hack or a kludge around the situation. My question is: is there an overload or another way baked into the .NET API to send an object to the client with a Bad Request HTTP status code?
I am using ASP.NET Web API version 5.2.4 targeting .NET Framework version 4.6.1.
You can use the Content<T>(...) method to do this. It returns a NegotiatedContentResult, which is serialized depending on the request headers (e.g. json, xml), and allows you to specify a HttpStatusCode.
You can use it like this:
return Content(HttpStatusCode.BadRequest, myObject);
If you wanted to, you could create your own BadRequest<T>(T obj) method in the controller as a wrapper, so then you could call it as you wanted:
public IHttpActionResult BadRequest<T>(T obj)
{
return Content(HttpStatusCode.BadRequest, obj);
}
public IHttpActionResult Action()
{
// do whatever validation here.
var validationResult = Validate();
// then return a bad request
return BadRequest(validationResult);
}
You can build/format the string in JSON format, pass it as string in the BadRequest() parameter and convert it to JSON again or any object on the caller's backend.
Haven't tried that but that should work.

Consuming a Mono<T> in a Spring web Controller

I'm currently trying to understand Reactor and refactored a service method that returned an Optional<CompanyDetails> to use Reactor's Mono<CompanyDetails> instead:
public Mono<CompanyDetails> findOne(String id) {
CompanyDetails result = retrieveFromSomewhere(id);
return Mono.justOrEmpty(result);
}
From my understanding this should emit either empty() or just(result).
I consume the service in a Spring web Controller like that:
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public DeferredResult<CompanyDetails> getCompany(#PathVariable String id) {
final DeferredResult<CompanyDetails> result = new DeferredResult<>();
companyService.findOne(id)
.consume(result::setResult);
return result;
}
This works fine if a result was found, but if findOne emits empty() it runs into a timeout. I could call get() explicitly and check for null, but that feels totally wrong.
Also: Before refactoring, getCompany threw a NotFoundException if no result was found, is that possible at all, or am I on the wrong track entirely?
Found the answer myself: First it turned out that Reactor's Mono has a toCompletableFuture-method and Spring MVC can also use that instead of DeferredResult. Failing the CompletableFuture throws an Exception just as expected.
To fail, Mono has to emit an error instead of empty:
public Mono<CompanyDetails> findOne(String id) {
CompanyDetails result = retrieveFromSomewhere(id);
return Mono
.justOrEmpty(result)
.otherwiseIfEmpty(Mono.error(...));
}
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public CompletableFuture<CompanyDetails> getCompany(#PathVariable String id) {
return companyService.findOne(id)
.toCompletableFuture();
}
Much better now.

Why Spring MVC does not allow to expose Model or BindingResult to an #ExceptionHandler?

Situation
I'm trying to group the code that logs the exceptions and render a nice view in a few methods. At the moment the logic is sometime in the #RequestHandler itself (in the a catch block), othertimes is delegated to an utility class (that works but moves the logic away from the place where the exception is thrown).
Spring's #ExceptionHandler seemed the way to group everything in one place (the controller itself or a parent) and get rid of some code (no need to put logic in the try-catch and no need for an utility class)... until I realized that an #ExceptionHandler methods won't have the ModelMap or BindingResult parameters autowired. Currently those objects are used to render the view with a sensible error message and we want to log some information contained in these objects as well.
Question
Why Spring doesn't not support method arguments such ModelMap or BindingResult for the #ExceptionHandler? What is the rationale behind it?
Possible solution
In the Spring source code (3.0.5) the arguments for the method are resolved in the HandlerMethodInvoker.invokeHandlerMethod. A an exception thrown by the request handler is caught there and re-thrown. The #ExceptionHandler and it's parameters are resolved elsewhere. As a workaround I thought to check if the Exception implements an hypothetical "ModelAware" or "BindingResultAware" interface, and in that case set Model and BindingResult attributes before re-throwhing it.
How does it sound?
As stated before you can raise an exception wrapping a binding result object in some method of your controller:
if (bindingResult.hasErrors()) {
logBindingErrors(bindingResult);
//return "users/create";
// Exception handling happens later in this controller
throw new BindingErrorsException("MVC binding errors", userForm, bindingResult);
}
With your exception defined as illustrated here:
public class BindingErrorsException extends RuntimeException {
private static final Logger log = LoggerFactory.getLogger(BindingErrorsException.class);
private static final long serialVersionUID = -7882202987868263849L;
private final UserForm userForm;
private final BindingResult bindingResult;
public BindingErrorsException(
final String message,
final UserForm userForm,
final BindingResult bindingResult
) {
super(message);
this.userForm = userForm;
this.bindingResult = bindingResult;
log.error(getLocalizedMessage());
}
public UserForm getUserForm() {
return userForm;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Next you just have to extract the required information from the raised then caught exception. Here assuming you have a suitable exception handler defined on your controller. It might be in a controller advice instead or even elewhere. See the Spring documentation for suitable and appropriate locations.
#ExceptionHandler(BindingErrorsException.class)
public ModelAndView bindingErrors(
final HttpServletResponse resp,
final Exception ex
) {
if(ex instanceof BindingErrorsException) {
final BindingErrorsException bex = (BindingErrorsException) ex;
final ModelAndView mav = new ModelAndView("users/create", bex.getBindingResult().getModel());
mav.addObject("user", bex.getUserForm());
return mav;
} else {
final ModelAndView mav = new ModelAndView("users/create");
return mav;
}
}
I ran into to same problem a while ago. The ModelMap or BindingResult are explicitly not listed as supported argument types in the JavaDocs of #ExceptionHandler, so this must have been intentional.
I reckon the reason behind it being that throwing exceptions in general could leave your ModelMap in an inconsistent state. So depending on your situation you might consider
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
If you're in control of the exception hierarchy you could hand over the BindingResult to the exception and extract it from the exception later for rendering purposes
Not throw an exception in the first place, but use some result code (just like BeanValidation does for example)
HTH
To improve the first answer:
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public VndErrors methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
List<ObjectError> globalErrors = ex.getBindingResult().getGlobalErrors();
List<VndError> errors = new ArrayList<>(fieldErrors.size() + globalErrors.size());
VndError error;
for (FieldError fieldError : fieldErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), fieldError.getField() + ", "
+ fieldError.getDefaultMessage());
errors.add(error);
}
for (ObjectError objectError : globalErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), objectError.getDefaultMessage());
errors.add(error);
}
return new VndErrors(errors);
}
There is already MethodArgumentNotValidException has already a BindingResult object, and you can use it, if you don't need to create an specific exception for this purpose.
I had the same problem to "add" FunctinalException to ourthe BindingResult
To resolve it, we use aop, if the controller method throws a runtime exception (or the one you want),
the aop catch it and update the bindingresult or model (if they are args of the method).
The method has to be annoted with a specific annotation containing the error path (configurable for specific exception if necessary).
It is not the best way because developer must not forget to add args that he don't use in its method but Spring does not provide a simple system to do this need.
I've wondered this too.
In order to handle bean validation in a way that allows for a non-global error view to display any ConstraintViolationExceptions that may be thrown, I opted for a solution along the lines of what #Stefan Haberl proposed:
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
I created a simple Action interface:
public interface Action {
String run();
}
And an ActionRunner class which does the work of ensuring ConstraintViolationExceptions are handled nicely (basically the messages from each ConstraintViolationException is simply added to a Set and added to the model):
public class ActionRunner {
public String handleExceptions(Model model, String input, Action action) {
try {
return action.run();
}
catch (RuntimeException rEx) {
Set<String> errors = BeanValidationUtils.getErrorMessagesIfPresent(rEx);
if (!errors.isEmpty()) {
model.addAttribute("errors", errors);
return input;
}
throw rEx;
}
}
}
Java 8 makes this pretty nice to run within the controller action method:
#RequestMapping(value = "/event/save", method = RequestMethod.POST)
public String saveEvent(Event event, Model model, RedirectAttributes redirectAttributes) {
return new ActionRunner().handleExceptions(model, "event/form", () -> {
eventRepository.save(event);
redirectAttributes.addFlashAttribute("messages", "Event saved.");
return "redirect:/events";
});
}
This is to wrap up those action methods for which I'd like to explicitly handle exceptions that could be thrown due to Bean Validation. I still have a global #ExceptionHandler but this deals with only "oh crap" exceptions.
Actually it does, just create an #ExceptionHandler method for MethodArgumentNotValidException.
That class gives you access to a BindingResult object.

Resources