Consuming a Mono<T> in a Spring web Controller - spring-mvc

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.

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!

Spring Webflux with Spring MVC - nulls emitted when using doOnEach

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.

spring mvc servlet 3 async response no database result in test

I'm testing the new servlet 3 asynchronous requests in my new project and get stuck while testing the controller.
I have a controller method like this:
#RequestMapping(value = "/thumbnails", method = RequestMethod.GET)
public Callable<ResponseEntity<List<Thumbnail>>> getAllThumbnails() {
//at this point I get results from the repository
return () -> {
//at this point I don't get any results
final List<Thumbnail> thumbnails = thumbnailRepository.findAll();
return ResponseEntity.ok(thumbnails);
};
}
And a corresponding test like this:
#Test
#Transactional
public void testGetAllThumbnails() throws Exception {
thumbnailRepository.saveAndFlush(thumbnail);
final MvcResult mvcResult = restThumbnailMockMvc.perform(get("/test/thumbnails"))
.andExpect(request().asyncStarted())
.andExpect(request().asyncResult(instanceOf(ResponseEntity.class)))
.andReturn();
mvcResult.getAsyncResult();
restThumbnailMockMvc.perform(asyncDispatch(mvcResult))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[*].id").value(hasItem(thumbnail.getId().longValue())))
.andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME)))
.andExpect(jsonPath("$.[*].fileName").value(hasItem(DEFAULT_FILE_NAME)));
}
The Repository and stuff is a simple spring data jpa bean and the whole configuration is based on spring boot.
If I query the controller in a normal way every thing works fine but in the test the repository returns no results.
Many thanks for your help on that in advanced, I can not find anything similar on the web.
Ok after a bit trial and error I figured out what went wrong.
The Problem is/was that the test method run in a transaction and for some reason the same transaction could not be accessed in the callable of the controller while the test runs. After removing the #Transactional annotation from the test method every thing works fine.

Recommended way to display an error message without resorting to #ModelAttribute with Spring MVC

I have the following method skeleton in a Spring MVC application:
#RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(#PathVariable("token") String token) {
...
}
I am trying to display an error message if the token is invalid for some reason. However I have no ModelAttribute in the method arguments and I don't really want one. But of course I can't use an Errors or BindingResults argument because of the absence of a ModelAttribute and its corresponding form.
So my question is:
what is the recommended way to display an error message given the above method signature and without introducing a ModelAttribute?
If the String you've returned from the method is a viewname (Spring default) then simply create a view for this case and do like:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
if(checkToken(token)){
doProcess();
return "userprofile";
} else {
return "badtoken"
}
}
In more complicated case you may have a hierarchy of exceptions, related to bad tokens. (Token is expired, token is just incorrect and so on). You can register an #ExceptionHandler in the same controller:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
return activate(token); // This method may throw TokenException and subclasses.
}
#ExceptionHandler(TokenException.class)
public ModelAndView tokenException(TokenException e){
// some code
return new ModelAndView("badtoken", "exception", e);
}

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