Create Spring 3 annotation that check Session for object and redirect the user - spring

I'm used to implement custom HandlerMethodArgumentResolverComposite for my projects, but now in some methods I have the repetitive code block
...
if (param != null){
return SiteMap.withRedirect(HOME); // resolves to "redirect:/home"
}
...
Is there a lean way to do this block outside from the controller methods?
Thanks in advance.
Answer to that:
HandlerMethod.getMethodAnnotation(Class<T>) will help a lot :D
https://gist.github.com/dgomesbr/5657473
public class UserRequiredAnnotationInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (handler == null)
{
return true;
}
if (((HandlerMethod) handler).getMethodAnnotation(RequiredUser.class) != null)
{
final Object userkey = request.getSession().getAttribute(LoginFilter.CURRENT_LOGGED_USER_ATTRIBUTE);
if (userkey == null)
{
response.sendRedirect(SiteMap.HOME_REDIRECT);
return false;
}
}
return true;
}
}

You could implement an interceptor with a preHandle() method.
The preHandle(..) method returns a boolean value. You can use this
method to break or continue the processing of the execution chain.
When this method returns true, the handler execution chain will
continue; when it returns false, the DispatcherServlet assumes the
interceptor itself has taken care of requests (and, for example,
rendered an appropriate view) and does not continue executing the
other interceptors and the actual handler in the execution chain.
However, since preHandle() doesn't return a String like your controller method does the interceptor implementation would be along the lines of
if (request.getParameter(yourParam) == true) {
return true;
} else (
response.sendRedirect(redirectPath);
return false;
}
Alternatively, you can of course put nearly the same code into a Servlet filter. The main difference is that the interceptor configuration in your MVC .xml files gives you more fine grained control over the "URL patterns" (i.e. your controller methods) the interceptor should be mapped to.

A spring interceptor would be the way to go.

Related

Spring ControllerAdvice - Fail to override handleHttpRequestMethodNotSupported() in ResponseEntityExceptionHandler

Here's a few facts for the situation that I'm currently facing
I have recently built a RestControllerAdvice with variousExceptionHandler as a global exception handler for my Spring RestController.
As I would like to return my customized response json for handling the pre-defined HTTP error as specified in ResponseEntityExceptionHandler, my RestControllerAdvice class inherits the ResponseEntityExceptionHandler and methods like handleHttpRequestMethodNotSupported(), handleHttpMessageNotReadable() are overriden.
I have successfully overridden handleHttpMediaTypeNotSupported() and handleHttpMessageNotReadable() but when it comes to handleHttpRequestMethodNotSupported(), I fail to do so.
Here's an excerpt of my code:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice(annotations=RestController.class)
public class TestRestExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Request Method Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Message Not Readable");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Media Type Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
}
The log for handleHttpRequestMethodNotSupported() is shown as follow:
[2019-06-05T17:49:50.368+0800][XNIO-74 task-7][WARN ][o.s.w.s.m.s.DefaultHandlerExceptionResolver] Resolved exception caused by Handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
The log for handleHttpMessageNotReadable() is shown as follow:
[2019-06-05T17:50:21.915+0800][XNIO-74 task-8][WARN ][o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] Resolved exception caused by Handler execution
As you can see, the successful code is handled by ExceptionHandlerExceptionResolver while the malfunction code is handled by DefaultHandlerExceptionResolver.
I am wondering what is the underlying reason and I will appreciate if someone can recommend any available solution. Thank you.
From the jackycflau answer, we can summarise as 2 questions.
Q1. Why removing annotations=RestController.class will works for HttpRequestMethodNotSupportedException
Q2. Why only HttpRequestMethodNotSupportedException is not caught?
To answer these 2 questions, we need to take a look to code on how spring handle exceptions. The following source code are based on spring 4.3.5.
During spring DispatcherServlet processing the request, when error occur, HandlerExceptionResolver will try to resolve the exception. In the given case, the exception is delegated to ExceptionHandlerExceptionResolver. The method to determine which method to resolve the exception is (getExceptionHandlerMethod in ExceptionHandlerExceptionResolver.java line 417)
/**
* Find an {#code #ExceptionHandler} method for the given exception. The default
* implementation searches methods in the class hierarchy of the controller first
* and if not found, it continues searching for additional {#code #ExceptionHandler}
* methods assuming some {#linkplain ControllerAdvice #ControllerAdvice}
* Spring-managed beans were detected.
* #param handlerMethod the method where the exception was raised (may be {#code null})
* #param exception the raised exception
* #return a method to handle the exception, or {#code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
}
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}
Since we are using #RestControllerAdvice, we only need to focus in the for loop, which determine which ControllerAdviceBean to use. We can see that the method isApplicableToBeanType will determine if the ControllerAdviceBean is applicable, and the related code are (ControllerAdviceBean.java line 149)
/**
* Check whether the given bean type should be assisted by this
* {#code #ControllerAdvice} instance.
* #param beanType the type of the bean to check
* #see org.springframework.web.bind.annotation.ControllerAdvice
* #since 4.0
*/
public boolean isApplicableToBeanType(Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
By reading the code, we can explain what is happening:
Answer for Q1
When annotations=RestController.class is removed, hasSelectors will return false, and hence isApplicableToBeanType will return true. So HttpRequestMethodNotSupportedException will be handled by TestRestExceptionHandler in this case.
Answer for Q2
For HttpRequestMethodNotSupportedException, DispatcherSerlvet can not find controller method to handle request. Hence handlerMethod passed to getExceptionHandlerMethod is null, then beanType passed to isApplicableToBeanType is also null and false is returned.
On the other hand, DispatcherSerlvet can find controller method for HttpMessageNotReadableException or HttpMediaTypeNotSupportedException. So the rest controller handler method will be passed to getExceptionHandlerMethod and isApplicableToBeanType will return true.
I have found out the culprit of the issue, which is regarding the #RestControllerAdvice annotation.
Orginally, I have annotated the class with #RestControllerAdvice(annotations=RestController.class).
After I remove the annotations key-value pair (i.e. just annotate the class with #RestControllerAdvice), HttpRequestMethodNotSupportedException is now successfully caught.
This is the solution that I can only be able to share. I don't understand the underlying reason and such behavior seems quite weird to me... Probably because the HttpRequestMethodNotSupportedException is not under the control by #RestController??? (just a wild guess). I will be happy if someone can give a full explanation on such behavior.

SpringMVC where to put common code between controller's methods

I'm working on an existing codebase and I'm seeing this pattern in all the controller methods. Same variables are declared in the beginning and the code is placed inside the try catch block which is also same across all the methods. I was wondering if there's a way to push the common code across methods inside a BaseController. So that I don't have to declare the common variables inside each method and the try catch block functionality is also delegated to someplace else.
At first, I created a BaseController class, annotated it with #Controller annotation and extended my controller to be its subclass. Then I moved all the common variables to the BaseController. The problem is, once I modify these variables inside the controller's method, they retain their values even in the next request which is problematic.
#RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public ResponseEntity delete(#PathVariable("id") Integer id)
{
HashMap response = new HashMap();
boolean success = false;
List errors = new ArrayList();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
String message = "";
try
{
purchaseInvoiceService.delete(id);
success = true;
message = "Purchase Invoice Deleted";
httpStatus = HttpStatus.OK;
} catch (Exception e)
{
errors.add(new ErrorMessage("error", e.getMessage()));
e.printStackTrace();
}
response.put("success", success);
response.put("errors", errors);
response.put("message", message);
return new ResponseEntity(response, httpStatus);
}
I want to refactor this patter so that in each method I just have to contain only the call to the service and conditionally setting the success and httpstatus variable (present in BaseController) and then returning the response using response() method present in BaseController which adds the data variable and it's return type is ResponseEntity.
Edit 1:
This endpoint returns a list of all purchase invoices, currently, its just returning the HashMap which gets converted to JSON. The point I'm trying to make is that these response, success, errors, httpStatus variables and the part where all these variables are put in response HashMap() are a part of every method inside each controller, I'd like to refactor these to something similar to ResponseFactory as well. So I'm thinking to pass the List to ResponseFactory as well which will then structure all the response and return in the form of ResponseEntity. Just want to know if I'm doing it correctly.
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity getAll() {
HashMap response = new HashMap();
boolean success = false;
List errors = new ArrayList();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
String message = "";
Map data = new HashMap();
try {
List<PurchaseInvoice> purchaseInvoices = purchaseInvoiceService.getAll();
data.put("purchaseInvoices", purchaseInvoices);
success = true;
message = "Purchase Invoice List";
httpStatus = httpStatus.OK;
} catch (Exception e) {
errors.add(new ErrorMessage("error", e.getMessage()));
e.printStackTrace();
}
response.put("success", success);
response.put("errors", errors);
response.put("message", message);
response.put("data", data);
return new ResponseEntity(response, httpStatus);
}
Your phrase: "Then I moved all the common variables to the BaseController" sounds confusing.
A controller in spring is just a Singleton with an additional "ability" provided by spring: its something that is exposed as a web endpoint (less relevant for your specific question).
Being Singleton means that there is one instance in the ApplicationContext.
So if the variables were moved like this:
class BaseController {
protected Field1 field1;
protected Field2 field2;
....
}
Then there is certainly a problem, you've actually introduced a state to the controller, and this state is shared among all the requests.
Long story short, don't create stateful controllers
Having said that the idea of refactoring is good. Just the way to refactor probably is wrong:
Instead, I suggest to consider the following refactoring:
Create class responseFactory with some static methods:
class ResponseFactory {
public static ResponseEntity success(String message) {
here prepare the map with boolean success and everything
}
}
Now the controller becomes:
class Controller {
public ResponseEntity delete(#PathVariable("id") Integer id) {
purchaseInvoiceService.delete(id); // I'll talk about exceptions later
return ResponseEntityFactory.success("Purchase Invoice Deleted");
}
}
Now as for exceptions - this is somewhat confusing - the code basically says that the response would be successful despite the errors.
So if you have to leave it like this, the ResponseEntityFactory will have to get the List of errors as well or something, but in general, Spring has a pretty powerful exception handling mechanism to map the exceptions thrown on the backend (service, DAO, whatever) to the meaningful non-200 response.

Spring controller advice does not correctly handle a CompletableFuture completed exceptionally

I am using Spring Boot 1.5, and I have a controller that executes asynchronously, returning a CompletableFuture<User>.
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private final UserService service;
#GetMapping("/{id}/address")
public CompletableFuture<Address> getAddress(#PathVariable String id) {
return service.findById(id).thenApply(User::getAddress);
}
}
The method UserService.findById can throw a UserNotFoundException. So, I develop dedicated controller advice.
#ControllerAdvice(assignableTypes = UserController .class)
public class UserExceptionAdvice {
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public String handleUserNotFoundException(UserNotFoundException ex) {
return ex.getMessage();
}
}
The problem is that tests are not passing returning an HTTP 500 status and not a 404 status in case of an unknown user request to the controller.
What's going on?
The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.
As stated in the CompletableFuture javadoc
[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]
In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(
Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors
So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.
A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T>. In this blog, an implementation of a possible Adapter was given.
public class DeferredResults {
private DeferredResults() {}
public static <T> DeferredResult<T> from(final CompletableFuture<T> future) {
final DeferredResult<T> deferred = new DeferredResult<>();
future.thenAccept(deferred::setResult);
future.exceptionally(ex -> {
if (ex instanceof CompletionException) {
deferred.setErrorResult(ex.getCause());
} else {
deferred.setErrorResult(ex);
}
return null;
});
return deferred;
}
}
So, my original controller would change to the following.
#GetMapping("/{id}/address")
public DeferredResult<Address> getAddress(#PathVariable String id) {
return DeferredResults.from(service.findById(id).thenApply(User::getAddress));
}
I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.
Hope it helps.
For those of you who still run into trouble with this : even though Spring correctly unwraps the ExecutionException, it doesn't work if you have a handler for the type "Exception", which gets chosen to handle ExecutionException, and not the handler for the underlying cause.
The solution : create a second ControllerAdvice with the "Exception" handler, and put #Order(Ordered.HIGHEST_PRECEDENCE) on your regular handler. That way, your regular handler will go first, and your second ControllerAdvice will act as a catch all.

Redirect using Spring boot interceptor

I have created a web app using spring boot and freemarker and implemented interceptor(HandlerInterceptorAdapter).
Inside the interceptor, when user is not logged then it will redirect to login page. This works fine. But the problem is that the controller is being executed first before redirecting to the login page.
My Interceptor Code:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
User userSession = (User) request.getSession().getAttribute("user");
if (userSession == null) {
response.sendRedirect("/login");
}
}
Controller class(after response.sendRedirect, this controller is still being excuted). Why? I'm stack in with this problem.
#RequestMapping("/home")
public String home(Model model, HttpServletRequest httpServletRequest) {
String returnPage = "home-admin";
User user = (User) httpServletRequest.getSession().getAttribute("user");
if(user != null){
String accessType = accessTypeRepository.getAccessType(user.getAccessId());
if(StrUtil.isEqual(accessType, AccessTypeConst.MANAGER.getType())){
returnPage = "home-manager";
}
}
return returnPage;
}
You should return false from your interceptor if you are done with execution.
Returns:
true if the execution chain should proceed with the next interceptor or the handler itself. Else, DispatcherServlet assumes that this interceptor has already dealt with the response itself.
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html
Change
if (userSession == null) {
response.sendRedirect("/login");
}
to
if (userSession == null) {
response.sendRedirect("/login");
return false;
}
In interceptor preHandle() function.
return false to let Spring framework assume that request has been handled by the spring interceptor itself and no further processing is needed.
return true to let Spring know to process the request through another spring interceptor or to send it to handler method (Your Controller Function) if there are no further spring interceptors.
So, In this case return false at end in interceptor preHandle function.
When i use return false, i take "Error: Exceeded maxRedirects. Probably stuck in a redirect loop http://localhost:8080/api/login"
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(true){
response.sendRedirect("/api/login");
return false;
}
return true;
}
for anyone who’s searching for the answer to the same question from #calisci
That’s probably cuz u r NOT excluding the redirect url from request
Try add this before redirect
if(!request.getRequestURL().toString().endswith("/put redirect url here")
Glad if help.

Spring aspects forward and cancel previous request

Here is my question
I want to intercept request before any spring controller call, check and modify request URI. After that it has to call another SPRING controller method.
I used mvc:interceptors however I want to configure it with annotations thats why I need a solution for #Aspect. Everything is working but controller called twice, for the original request and for the new request. Using interceptors I return false and it cancels it, how do I do about Aspect classes? Thank you
Here is my code:
#Component
#Aspect
public class TestAspect {
#Before("execution(* mycontroller.*(..)) &&" + "args(request,response)")
public void interceptUrl(HttpServletRequest request, HttpServletResponse response) {
System.out.println("#Aspect is running!");
System.out.println(request.getRequestURI());
if (request.getAttribute("client") == null) {
request.setAttribute("client", "test");
request.getRequestDispatcher("/newpath/contact").forward(request, response);
}
}
}
You should consider #Around advice instead of #Before. In this case you can simply not execute original request chain.

Resources