Security Context is null with CompletableFuture - spring-mvc

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!

Related

contractVerifierMessaging.receive is null

I'm setting up contract tests for Kafka messaging with Test Containers in a way described in spring-cloud-contract-samples/producer_kafka_middleware/. Works good with Embedded Kafka but not with TestContainers.
When I try to run the generated ContractVerifierTest:
public void validate_shouldProduceKafkaMessage() throws Exception {
// when:
triggerMessageSent();
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("kafka-messages",
contract(this, "shouldProduceKafkaMessage.yml"));
Cannot invoke "org.springframework.messaging.Message.getPayload()" because "receive" is null
is thrown
Kafka container is running, the topic is created. When debugging receive method I see the message is null in the message(destination);
Contract itself:
label("triggerMessage")
input {
triggeredBy("triggerMessageSent()")
}
outputMessage {
sentTo "kafka-messages"
body(file("kafkaMessage.json"))
Base test configuration:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestConfig.class, ServiceApplication.class})
#Testcontainers
#AutoConfigureMessageVerifier
#ActiveProfiles("test")
public abstract class BaseClass {
What am I missing? Maybe a point of communication between the container and ContractVerifierMessage methods?
Resolved the issue by adding a specific topic name to listen() method in KafkaMessageVerifier implementation class.
So instead of #KafkaListener(id = "listener", topicPattern = ".*"), it works with:
#KafkaListener(topics = {"my-messages-topic"})
public void listen(ConsumerRecord payload, #Header(KafkaHeaders.RECEIVED_TOPIC)

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.

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.

Mockito verify post object has same values not same object reference

This is my test:
#Test
public void shouldProcessRegistration() throws Exception {
Spitter unsaved = new Spitter("Gustavo", "Diaz", "gdiaz", "gd123");
Spitter saved = new Spitter(24L, "Gustavo", "Diaz", "gdiaz", "gd123");
SpitterRepository spittlerRepository = Mockito.mock(SpitterRepository.class);
Mockito.when(spittlerRepository.save(unsaved)).thenReturn(saved);
SpitterController spittleController = new SpitterController(spittlerRepository);
MockMvc mockSpittleController = MockMvcBuilders.standaloneSetup(spittleController).build();
mockSpittleController.perform(MockMvcRequestBuilders.post("/spitter/register")
.param("firstName", "Gustavo")
.param("lastName", "Diaz")
.param("userName", "gdiaz")
.param("password", "gd123"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/" + saved.getUserName()));
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(unsaved);
}
This is my controller:
#Controller
#RequestMapping(value = "spitter")
public class SpitterController {
SpitterRepository spitterRepository;
#Autowired
public SpitterController(SpitterRepository spittlerRepository) {
this.spitterRepository = spittlerRepository;
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(Spitter spitter){
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUserName();
}
}
I want to verify that spitterRepository.save was called passing the same unsaved object I defined in the test. But i'm getting this exception:
Argument(s) are different! Wanted:
spitterRepository.save(
spittr.Spitter#3bd82cf5
);
-> at spitter.controllers.test.SpitterControllerTest.shouldProcessRegistration(SpitterControllerTest.java:48)
Actual invocation has different arguments:
spitterRepository.save(
spittr.Spitter#544fa968
);
Use an ArgumentCaptor to capture the value passed to save, and then assert on it.
ArgumentCaptor<Spitter> spitterArgument = ArgumentCaptor.forClass(Spitter.class);
verify(spittlerRepository, atLeastOnce()).save(spitterArgument.capture());
assertEquals("Gustavo", spitterArgument.getValue().getName());
For asserting if the Bean is the same, I would recommend you to use Hamcrest's samePropertyValues (http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/beans/SamePropertyValuesAs.html)
If I understand correctly, the below line initializes a mock where you can utilize the capabilities in Spring.
MockMvc mockSpittleController = MockMvcBuilders.standaloneSetup(spittleController).build();
When you call the mock like below, you will not call the method with the unsaved object. Rather, I guess a new object will be created.
mockSpittleController.perform(MockMvcRequestBuilders.post("/spitter/register")
.param("firstName", "Gustavo")
.param("lastName", "Diaz")
.param("userName", "gdiaz")
.param("password", "gd123"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/" + saved.getUserName()));
This makes the verification fail, because the instances will not be the same.
To solve this, you should make sure that Spitter implements equals() and then use the eq() matcher for the verification:
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(org.mockito.Matchers.eq(unsaved));
This will check if the expected argument equals() what was passed.
import org.mockito.Matchers;
//...
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(Matchers.refEq(unsaved));

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.

Resources