RestTemplate does send null values in object - spring

I have written a very simple EmailService using Spring boot.
A controller received an EmailRequest and sends the Mail:
#RestController
#Slf4j
public class EmailController {
private final EmailService emailService;
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
#PostMapping
public ResponseEntity sendEmail(EmailRequest request) {
try {
emailService.sendEmail(request);
} catch (Exception e) {
log.error("Error while sending email", e);
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
}
I want to test the behaviour in another controller as follows:
#Controller
public class TestController {
#GetMapping
public ResponseEntity testSend(){
EmailRequest request = new EmailRequest();
request.setTo("roger#example.com");
request.setBody("This is the body");
request.setSubject("This is a subject");
return new RestTemplate().postForObject("http://localhost:9999",request,ResponseEntity.class);
}
}
Now i set two breakpoints, one before sending the request via RestTemplate. As expected, the values are filled correctly.
Another breakpoint in the receiving controller is showing all values of EmailRequest null.
When I use PostMan to call the sending controller its working fine, so I probably doing something wrong with the RestTemplate.
Does anyone know what might be the problem?

#RequestBody missing for #PostMapping
#PostMapping
public ResponseEntity sendEmail(#RequestBody EmailRequest request) { }

Related

Spring Boot - Store current user in global variable and initialise from API call when #service bean is created

I am creating a microservice architectured project with Zuul as gateway. I have all authentication handled in a service called common-service. I have exposed a API from common-service to return current logged in user. This is working fine.
Now, I have another microservice called inventory. In service class of inventory, I want to use current loggedin username in multiple methods. So, I am making a webclient call to common-service and getting current username. This is working fine but I am making a webclient API call to common service everytime I require username. Example - if I add a new entry, doing API call, then on update again API call etc. this seems not to be an optimised way
so problem is - I want to make this API call at global level. i.e. whenever my service bean is autowired, this API call should be made and username should be store somewhere which I can use across methods in my service call.
I tried #PostConstruct and #SessionAttributes but not able to get exact problem solved.
Can somebody help me with best suited solution or concept for handling this issue.
Below are code snippets
public class LeadService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
UserDetailsService userDetailsService;
//more autowiring
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(userDetailsService.getCurrentUser().getId());
lead.setCreatorId(userDetailsService.getCurrentUser().getId());
}
else if(type.equalsIgnoreCase("update"))
{
//some logic
}
}
private StatusEnum setLeadStatus(Lead lead, StatusEnum status,String string)
{
LeadStatus lstatus=null;
switch(string)
{
case "create":
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
break;
case "udpate":
lstatus= lsRepo.FindLeadStatusByLeadID(lead.getLeadId()).get(0);
if(!lstatus.getStatus().equals(lstatus))
{
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
}
break;
}
return lstatus.getStatus();
}
private Address setAddress(#Valid LeadCreateData payload,Address address)
{
//some setters
address.setCreator(userDetailsService.getCurrentUser().getId());
return aRepo.save(address);
}
As you can see, I am using userDetailsService.getCurrentUser().getId() in many places. I am getting this id from below autowired method. But my one API call is required everytime I need this id.
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
HttpServletRequest request;
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser()
{
UserReturnData userDetails = webClientBuilder.build()
.get()
.uri(reqUrl+"user/me")
.header("Authorization", request.getHeader("Authorization"))
.retrieve()
.bodyToMono(UserReturnData.class)
.block();
return userDetails;
}
}
I want a optimal way where I can call this API method to get current user only once. and I can use it throughout my #service class.
Create OncePerPrequestFilter or GenericFilterBean which has your UserDetailsService autowired.
And also you want to create something similar to RequestContextHolder or SecurityContextHolder which can hold your UserReturnData in a ThreadLocal variable. Look at those two spring classes to get idea but yours can be much simpler. Lets call it UserReturnDataContextHolder.
In the filter, you created in step1, when the request comes in populate it and when the response is leaving, clear it.
Now you can access it anywhere in the service via UserReturnDataContextHolder.getUserReturnData() and you are not making multiple calls either
Edit: The section below is contributed by Sridhar Patnaik as reference -
Below code to get it working
Added a class to store currentuserid
public class CurrentUser
{
private Long currentUserId;
//getter setter
}
Added a current user filter to intercept request and fetch current user.
public class CurrentUserFilter implements Filter
{
#Autowired
private CurrentUser currentUser;
#Autowired
UserDetailsService UserDetailsService;
#Override
public void init(FilterConfig arg0) throws ServletException {
// NOOP
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try
{
this.currentUser.setCurrentUserId(UserDetailsService.getCurrentUser().getId());
chain.doFilter(servletRequest, servletResponse);
}
finally
{
this.currentUser.clear();
}
}
#Override
public void destroy() {
// NOOP
}
}
Added required AppConfig
#Configuration
public class AppConfig
{
#Bean
public Filter currentUserFilter() {
return new CurrentUserFilter();
}
#Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(this.currentUserFilter());
result.setUrlPatterns(Lists.newArrayList("/*"));
result.setName("Tenant Store Filter");
result.setOrder(1);
return result;
}
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
#Bean(name = "tenantStore")
#Scope(scopeName = "prototype")
public CurrentUser tenantStore() {
return new CurrentUser();
}
}
And then autowired CurrentUser to my existing service class.
{..
#Autowired
CurrentUser currentUser;
...
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(currentUser.getCurrentUserId());
lead.setCreatorId(currentUser.getCurrentUserId());
lead.setAddress(setAddress(payload, new Address()));
}
else if(type.equalsIgnoreCase("update"))
{
lead.setAsigneeId(userDetailsService.getUserFromId(payload.getAssigneeId()).getId());
lead.setAddress(setAddress(payload,lead.getAddress()));
}
}

Spring boot - Pass argument from interceptor to method in controller

For learning purposes, I have made a custom authentication system where I pass a token from the client to the server through the Authorization header.
In the server side, I'd like to know if it's possible to create in the interceptor, before the request reaches a method in the controller, an User object with the email from the token as a property, and then pass this user object to every request where I require it.
This what I'd like to get, as an example:
#RestController
public class HelloController {
#RequestMapping("/")
public String index(final User user) {
return user.getEmail();
}
}
public class User {
private String email;
}
Where user is an object that I created in the pre-interceptor using the request Authorization header and then I can pass, or not, to any method in the RestController.
Is this possible?
#Recommended solution
I would create a #Bean with #Scope request which would hold the user and then put the appropriate entity into that holder and then take from that holder inside the method.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}
and then
#Component
public class MyInterceptor implements HandlerInterceptor {
private CurrentUser currentUser;
#Autowired
MyInterceptor(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.currentUser.setCurrentUser(new User("whatever"));
return true;
}
}
and in the Controller
#RestController
public class HelloController {
private CurrentUser currentUser;
#Autowired
HelloController(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#RequestMapping("/")
public String index() {
return currentUser.getCurrentUser().getEmail();
}
}
#Alternative solution
In case your object that you would like to have, only contains one field, you can just cheat on that and add that field to the HttpServletRequest parameters and just see the magic happen.
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TRY ONE AT THE TIME: email OR user
//BOTH SHOULD WORK BUT SEPARATELY OF COURSE
request.setAttribute("email", "login#domain.com");
request.setAttribute("user", new User("login#domain.com"));
return true;
}
}
You can use a local thread context object as follows - which will be handling one parameter per request thread (thread safe):
public abstract class LoggedUserContext {
private static ThreadLocal<User> currentLoggedUser = new ThreadLocal<>();
public static void setCurrentLoggedUser(User loggedUser) {
if (currentLoggedUser == null) {
currentLoggedUser = new ThreadLocal<>();
}
currentLoggedUser.set(loggedUser);
}
public static User getCurrentLoggedUser() {
return currentLoggedUser != null ? currentLoggedUser.get() : null;
}
public static void clear() {
if (currentLoggedUser != null) {
currentLoggedUser.remove();
}
}
}
Then in the interceptor prehandle function:
LoggedUserContext.setCurrentLoggedUser(loggedUser);
And in the interceptor postHandler function:
LoggedUserContext.clear();
From any other place:
User loggedUser = LoggedUserContext.getCurrentLoggedUser();

FeignClient RequestParam without URLdecode

I am trying to use Spring boot to communicate with a backend server which does not support encoded URL. I tried intercepting the RestTemplate and modifying the query parameter but it does not seems to work. What should be the proper way to doing it?
The code for feign client is
#FeignClient(url = "${gateway.api}",
configuration = BackendConfig.class)
#RequestMapping("/v1/")
public interface GatewayClient {
#GetMapping(path = "/authorize")
String getAuthorization(#RequestParam(name = "cburl") String url);
}
Now if I invoke GatewayClient.authorize("http://example.com") I can see that it gets called ${gateway.api}/v1/authorize?cburl=http:%2F%2Fexample.com which is not recognized by the backend service. However, $(gateway.api}/v1/authorize?cburl=http://example.com works.
The BackendConfig class is given below for reference
class BackendConfig {
#Autowired
ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Decoder springDecoder() { return new ResponseEntityDecover(new SpringDecoder(messageConverters); }
#Bean
public MyInterceptor requestInterceptor() {
return new MyInterceptor();
}
public class MyInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
String lines;
try {
lines = URLDecoder.decode(String.valueOf(template.queries().get("url")), "UTF-8");
template.queries.put("url", Collections.singletonList(lines));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
But I am getting UnsupportedOperationException and I believe at this point I can not modify the queries. Any suggestion is highly appreciated. (You will notice that the query parameter is leaving ':' (colon) as it is instead of encoding it to %3A).

Make Spring's #RequestBody annotation return a custom response on-fail

I have a controller as follows:
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {}
However, when the object transformation fails, the API automatically returns a 400 with the Java stack trace. How can I modify this on-failure response? (I wish to change or remove the response message).
Here is an example how to do this with an #ExceptionHandler annotation
#RestController
public class Controller {
#RequestMapping(value = "/", method = RequestMethod.POST)
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {
//
}
#ControllerAdvice
public class RestEndpointExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleNotValidExceptionException(HttpServletRequest req, MethodArgumentNotValidException ex) {
Object customException = "Validation failed";
return new ResponseEntity<Object>(customException, HttpStatus.BAD_REQUEST);
}
}
}
I pushed the code in here
You can use #ExceptionHandler with #ResponseStatus and leave handler empty so that only Status Code is returned back.
#ExceptionHandler(EmptyResultDataAccessException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public void notFoundException() {
}

Spring MVC Index Controller Strict Request

Trying to map the index controller correctly.
#Controller
#RequestMapping("/")
public class ClientIndexController
{
#RequestMapping(method=RequestMethod.GET)
public ModelAndView index()
{
}
}
or
#Controller
public class ClientIndexController
{
#RequestMapping("/")
public ModelAndView index(HttpServletRequest request)
{
}
}
These both approaches could not distinguish two different requests.
http://domain.com/
http://domain.com/?test=1 - in this case 404 must be thrown.
How can I avoid such behavior?
You can have Map with all request parameters, and check if the map is empty. Then you can implement a lot of different ways in creating a 404 (the one in the example below in only one way (maybe not the best)).
#Controller
#RequestMapping("/")
public class ClientIndexController {
#RequestMapping(method=RequestMethod.GET)
public ModelAndView index(#RequestParam Map<String,String> allRequestParams) {
if(allRequestParams != null && !allRequestParams.isEmpty() {
throw new ResouceNotFoundException();
}
}
#ExceptionHandler(ResouceNotFoundException.class)
#ResponseStatus(404)
public void RprocessValidationError(ResouceNotFoundException ex) {
}
}
If you only want to check that a special parameter is not there then you could use
#RequestMapping(method = RequestMethod.GET, params="!test")
public ModelAndView index(){...}

Resources