Databinding in the controller using spring throws exception - spring

I have a controller with method parameter as model say
public Response create(Customer customer){
}
Customer model :customer model looks like
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME,property = "type")
#JsonSubTypes({#Type(value = Config.class, name = "IPC")})
public class Customer(){
private String type; }
From swagger UI if i send type as IPC its works fine, but any other value than IPC throws an 400 exception while binding.How can i catch this exception inside controller

try to use the #ExceptionHandler annotation
The documentation of Spring 4 (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-exceptionhandler) states that
The HandlerExceptionResolver interface and the
SimpleMappingExceptionResolver implementations allow you to map
Exceptions to specific views declaratively along with some optional
Java logic before forwarding to those views. However, in some cases,
especially when relying on #ResponseBody methods rather than on view
resolution, it may be more convenient to directly set the status of
the response and optionally write error content to the body of the
response.
You can do that with #ExceptionHandler methods. When declared within a
controller such methods apply to exceptions raised by #RequestMapping
methods of that controller (or any of its sub-classes). You can also
declare an #ExceptionHandler method within an #ControllerAdvice class
in which case it handles exceptions from #RequestMapping methods from
many controllers. Below is an example of a controller-local
#ExceptionHandler method:
So, in your controller you can have a method like this
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleArgumentNotValid(MethodArgumentNotValidException e, ModelMap map, HttpServletRequest request) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
//you can get the exception e,
//you can get the request
//handle whatever you want the then return to view
return "your view that you will handle the exception";
}

Related

Is it possible to trigger validations when calling RestController method from outside?

So far I've got an endpoint which goes as follows:
#PostMapping(path = "/my-endpoint")
public ResponseEntity<Void> method(#PathVariable("id") String id,
#RequestBody #Valid MyClass<MyType> body) {
// custom logic in here
return ResponseEntity.ok().build();
}
When performing the POST request to that endpoint, the validation when the object is wrong is performed properly and 400: Bad Request is shown.
However, now due to some code circumstances I want to trigger that method from outside the RestController and perform the same validations via a Consumer.
The new code goes as follows:
#Bean
public Consumer<Message<String>> consumer(MyController myController) {
message -> myController.method("sampleId", message); // message here is parsed to the class, so the proper type is sent to the controller method.
}
And whenever I check for the myController.method call, the code is always 200: OK, no matter what input is sent.
Is there a way to trigger validations not sent through the REST API?
I suggest to move custom logic from controller to a #Service annotated class first.
Then inject validator #Autowired private Validator validator; and trigger validation.
public void myServiceMethod(MyMessage message) {
Set<ConstraintViolation<MyMessage>> violations = validator.validate(message);
if (!violations.isEmpty()) {
// ...
}
}
https://www.baeldung.com/spring-service-layer-validation

resilience4j circuit breaker change fallback method return type than actual called method return type

I am trying to learn Spring Boot microservices. Now I am trying to implement circuit breaker with resilience4j if any of my called service is off.
If I set the fallback method return type as like the actual method return type than it works fine but I can't show the information that my service is off. Because it then send the response null in object's fields. But if I change the return type to String on actual method and also in fallback then I will not be able to get the object value as JSON.
Is it possible to return as string something like Branch service is down!.. with my fallback method and if OK then get the object value as JSON from actual called method? My attempts are below:
My controller method:
#GetMapping("/getById/{id}")
#CircuitBreaker(name = "default", fallbackMethod = "employeeFallback")
public ResponseModelEmployee getEmployee(#PathVariable("id") Long id) {
return employeeService.findByEmployeeId(id);
}
My fallback method in controller:
public ResponseModelEmployee employeeFallback(Long id, Exception ex) {
return new ResponseModelEmployee();
}
My service method called from controller:
public ResponseModelEmployee findByEmployeeId(Long id) {
ResponseModelEmployee empDetails = new ResponseModelEmployee();
...
Branch branch = restTemplate.getForObject("http://BRANCH-SERVICE/branch/getById/" +
employee.get().getBranchId(),
Branch.class);
...
return empDetails;
}
My desire method as fallback:
public String employeeFallback(Long id, Exception ex) {
return "Branch Service is down";
}
If I set my desire method for fallback then it gives the following error:
java.lang.NoSuchMethodException: class com.example.employee.VO.ResponseModelEmployee class com.example.employee.controller.EmployeeController.employeeFallback(class java.lang.Long,class java.lang.Throwable) at io.github.resilience4j.fallback.FallbackMethod.create(FallbackMethod.java:92) ~[resilience4j-spring-1.7.0.jar:1.7.0] ....
Resilince4j expects the fallback method to have the same return type as of the actual method.
Documentation says:
It's important to remember that a fallback method should be placed in
the same class and must have the same method signature with just ONE
extra target exception parameter).
If there are multiple fallbackMethod methods, the method that has the
most closest match will be invoked, for example:
If you try to recover from NumberFormatException, the method with
signature String fallback(String parameter, IllegalArgumentException
exception)} will be invoked.
You can define one global fallback method with an exception parameter
only if multiple methods has the same return type and you want to
define the same fallback method for them once and for all.
So, you cannot directly change the return type to a different one.
You can try few suggestions:
Add #CircuitBreaker and fallback to the service method.
Change return type of service method and fallback method to Object.
One more way could be , you can keep the return type as it is but add a String type message object to response model ResponseModelEmployee. Then set message string to it during fallback.
Another solution could be to return ResponseEntity from the from the method where rest call is made and in the fallback method use ResponseEntity<?> as response object.
you can use Object as a return type
in my case for example:
#GetMapping("/getUser/{id}")
#CircuitBreaker(name= something , fallbackMethod = "ContactsServiceDown")
public ResponseEntity<User> getDetailsById(#PathVariable(id)){
//some logic
return new ResponseEntity<User>(user , HttpStatus.OK);
}
public ResponseEntity<Object> ContactsServiceDown(int id , Exception e){
//some logic
return new ResponseEntity<Object>("ContactsServersDown", HttpStatus.Forbidden)
}
or in returnType ResponseEntity<> leave the type Field empty, hopefully it may work!

#ExceptionHandler not working in a Service class

The below is a service class
#Service
class Test {
public Object findEmployees1(String id, String dbId) {
return employeeRepo.findByDatabaseIdAndIdentifier(dbId, id);
}
public Object findEmployees2(String name, String dbId) {
Object records = employeeRepo.findByNameAndDatabaseId(name, dbId);
}
#ExceptionHandler(Exception.class)
public void internalErrorExceptionHandler(Exception ex) {
LOGGER.error("Log the exception here");
throw new InternalErrorException(ex.getMessage());
}
}
I want if any exception(eg some SQLException) comes in any of the method in the class test , it gets caught by the #ExceptionHandler and is logged there and re thrown
I cannot use #ExceptionHandler with #ControllerAdvice as I don't have any class with #Controller annotation on it . I am writing just a common module to get data from the database.
When I execute the code , the log under #ExceptionHandler is not printed as it never gets called.
The requirement is to log and rethrow a common exception if any exception comes in any of the service class methods without having individual try catch statements in each method.
As suggested, you can use spring aop to handle such exceptions. Instead of duplicating I will link to the existing question I have tried to answer here.
The other way is, if you are using ByteBuddy, you can use following annotation on the method which is expected to throw exception.
#Advice.OnMethodExit(onThrowable = RuntimeException.class)

Spring AOP & HttpServletRequest

I am working on an annotation that will be sending some audit events to the other microservice.
Say, I am creating an entity and I have a method add on my Rest controller.
#PostMapping
#Audit
public ResponseEntity<EntityDTO> add(EntityDTO entity){
...
}
I have an appropriate Aspect defined, that is associated with the #Audit annotation.
But here is a trick, the nature of an audit event dictates that I need to extract some metadata from the HttpServletRequest itself.
And I do not want to modify my signature by adding (or replacing my only argument) HttpServletRequest object.
How can I pass HttpServletRequest into my aspect? Is there some elegant way?
Since you're using spring MVC, consider Spring MVC interceptors instead of "generic" aspects.
These are natively supported by Spring MVC and can provide access to both the handler and HttpServletRequest object
See this tutorial for using the interceptors and general configuration
See This thread for some information about the handler
final HandlerMethod handlerMethod = (HandlerMethod) handler; // this is what you'll get in the methods of the interceptor in the form of Object
final Method method = handlerMethod.getMethod();
Following is how it can be done with Spring AOP .
Example annotation.
#Retention(RUNTIME)
#Target({ TYPE, METHOD })
public #interface Audit {
String detail();
}
And the corresponding aspect
#Component
#Aspect
public class AuditAspect {
#Around("#annotation(audit) && within(com.package.web.controller..*)")
public Object audit(ProceedingJoinPoint pjp, Audit audit) throws Throwable {
// Get the annotation detail
String detail = audit.detail();
Object obj = null;
//Get the HttpServletRequest currently bound to the thread.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
try {
// audit code
System.out.println(detail);
System.out.println(request.getContextPath());
obj = pjp.proceed();
} catch (Exception e) {
// Log Exception;
}
// audit code
return obj;
}
}
NB : Op has accepted interceptor based answer. This answer is to demonstrate the Spring AOP code to achieve the requirement.
Hope this helps

Migrating from SimpleFormController based implementation in Spring 2.5 to annotation based Controllers in Spring 4

I am facing an issue while migrating from spring 2.5 to 4. Earlier my code was using implementations of SpringForController and since they are deprecated, I need to switch to annotation based controller configuration. These are some of the overridden methods used in spring 2.5 :
protected Map referenceData(HttpServletRequest request, Object command,
Errors errors) throws Exception {
//custom code
}
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
MyObject obj = (MyObject) command;
// custom code
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) {
super.initBinder(request, binder);
//custom code
}
protected void onBindAndValidate(HttpServletRequest request,
Object command, BindException error) throws Exception {
super.onBindAndValidate(request, command, error);
// custom code
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
//custom code
}
I have annotated the referenceData method using getMapping and tweaked the code for it to render a form. The same goes for InitBinder method as well. As for the post method(onSubmit method), I have done the following :
#RequestMapping(value = "myCustomUrl", method = RequestMethod.POST)
protected ModelAndView onSubmit(HttpServletRequest request, #ModelAttribute("command")
EmployeeForm employeeForm)
throws Exception {
//custom code
}
The issue I am facing is with the binding of data. The object EmployeeForm contains a lot more fields that doesn't have corresponding fields in the form being submitted. And because of this those values are being set to the default values(null if it is an object and 0 if it is a number). These fields in EmployeeForm may be populated through separate requests. But when I submit the above form, all the fields in EmployeeForm that are not in the form request will be taken as null(or their default values depending on the type of data). This was handled in spring 2.5 using the formBackingObject by which the fields in the command Object is bound to. Is there a way to do the same than getting the respective entity from the database and setting each field submitted by the form manually in to the entity?
As for the logic written in onBindAndValidate, should I use annotations with custom validator in the model class for validating or is there any other way to make it work as there are many custom code in it other than that deals with validation.

Resources