I am handling negative cases like calling GET API which is actually a POST call. This gives Method Not Found Error with 405 status by Spring.
But I want my own exception so I added the following resolver:
public class HandlerExceptionResolver
implements org.springframework.web.servlet.HandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(this.getClass());
#Override
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
Object handler,Exception exception) {
logger.trace("-------------doResolveException-------------");
System.out.println("-------------doResolveException-------------");
if(exception instanceof HttpRequestMethodNotSupportedException) {
Fault fault = new Fault();
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
String errorMessage;
try {
errorMessage = mapper.writeValueAsString(fault);
response.setStatus(405);
response.setContentType("application/json");
response.getWriter().println(errorMessage);
response.getWriter().flush();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
But this doesn't writes the JSON to response.
I don't think you're supposed to want to write your own output using that response parameter. Spring expects your method to return a ModelAndView instance, which you can populate with the data you want to send to the user (the model part) and the name of the view resource that will be used to render it. You would then also need to define a new view resource that will just generate the JSON you want, out of the data in the ModelAndView...
As for concern can you debug out the value of
errorMessage = mapper.writeValueAsString(fault);
there is better way to do this exception handling.
#ExceptionHandler(HandlerExceptionResolver.class)
public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
logger.error("Requested URL="+request.getRequestURL());
logger.error("Exception Raised="+ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", request.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
You can try also if you want to use #ExceptionHandler to handle this
situation.
Related
There is an Exception that is thrown when an user is not found in database and i'd like to handle that particular exception from the controller perspective layer in a separated method by #ExceptionHandler annotation without losing the original data sent by the user. Well, so, i'm using Sessions and my first attempt was trying to get the object back from it by HttpServletRequest but i got:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'pontoEditar' available as request attribute
The code:
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
// I'd like to get the original object back ...
return "pontos/editar";
}
How would it be if i use a try-catch block
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = null;
try {
pontoConvertido = pontoConverter.convert(ponto);
catch (ConversionFailException ex) {
attr.addFlashAttribute("error", "User not found!");
return "redirect:/ponto/listar";
}
// ...
return "redirect:/ponto/listar";
}
Here the simplified code:
public class ConversionFailException extends RuntimeException {
public ConversionFailException(String mensagem) {
super(mensagem);
}
}
Controller with POST.
The exception happens in the POST at line with: Ponto pontoConvertido = pontoConverter.convert(ponto);
#Controller
#SessionAttributes("pontoEditar")
#RequestMapping("/ponto/editar")
public class PontoEditarController {
// ... GET Removed.
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = pontoConverter.convert(ponto);
// ...
return "redirect:/ponto/listar";
}
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
attr.addFlashAttribute("falha", "Usuário não foi encontrado");
/* I tried but it failed, how can i get ? */
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
return "pontos/editar";
}
#GetMapping("pontoEditar")
public PontoEditarDTO getPontoModel() {
return new PontoEditarDTO();
}
}
You can add WebRequest (or HttpSession, etc...) as a parameter in your exception handler, it will be injected by Spring.
You can have a look at the documentation here to see what parameter can be injected by Spring when the handler is called.
I am hitting one API using RestTemplate exchange method, Here I am getting responseEntity of ClientResponse Type. If we have any Bad request in first line of code, I'll get 400 and cursor will go to the catch and throwing Error. So remaining code(For setting a response Data) is not executing .Instead of this I want to set the response Data and I want to set status code also and want to execute remain code. How we can do it, Do we need to use Flag variable ??
ResponseEntity<ClientResponse> responseEntity = this.getRestTemplate().exchange(API_URL,
HttpMethod.POST, entity, ClientResponse.class);
response.setResponseEntity(responseEntity);
response.setValue(inputRequest.getValue));
response.setEndTime(LocalDateTime.now());
response.setRequestPayload(gson.toJson(inputRequest));
response.setHttpMethod(HttpMethod.POST);
response.setRequestHeaders(entity.getHeaders().toSingleValueMap());
} catch (Exception e) {
LOG.error("error occurred in service" + e.getMessage());
}
return response;
You can use restTemplate error handler for that. You can parse the error response returned from the rest template like 404 or some other error.
You can use those status code and error response to rest the response as per your need.
For that you need to define a class by implementing ResponseErrorHander.
public class ServiceResponseErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().is4xxClientError() ||
response.getStatusCode().is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
#SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(
ResponseEntity.status(response.getRawStatusCode())
.headers(response.getHeaders())
.body(errorObject)
);
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
And use it in your rest template bean like this.
RestTemplateResponseErrorHandler errorHandler = new
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
I wish to code the Rest Controller in spring-boot for my webhook. I am creating a google action, with simple actions.
This is a boilerplate: https://github.com/actions-on-google/dialogflow-webhook-boilerplate-java/blob/master/src/main/java/com/example/ActionsServlet.java.
I want to do the same, only in spring-boot. I want to manipulate JSON body as input, but not sure how to do this.
#RestController
public class indexController extends HttpServlet {
#Autowired
private App actionsApp;
//handle all incoming requests to URI "/"
// #GetMapping("/")
// public String sayHello() {
// return "Hi there, this is a Spring Boot application";}
private static final Logger LOG = LoggerFactory.getLogger(MyActionsApp.class);
//handles post requests at URI /googleservice
#PostMapping(path = "/", consumes = "application/json", produces = "application/json")
public ResponseEntity<String> getPost(#RequestBody String payload,
#RequestHeader String header, HttpServletResponse response) throws IOException {
//Not sure what to do here.
System.out.println(jsonData);
return ResponseEntity.ok(HttpStatus.OK);
try {
//writeResponse(response, jsonResponse);
//String med request body og object that has all request header entries
String jsonResponse = actionsApp.handleRequest(body, listAllHeaders(header)).get();
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
} catch (
InterruptedException e) {
System.out.println("Something wrong happened, interupted");
} catch (
ExecutionException e) {
System.out.println("Something wrong happened, execution error");
}
}
First, there is an error in your code. There might be a wrong "return" before your function logic.
return ResponseEntity.ok(HttpStatus.OK);
Second, as you are using Spring Framework, and you use "#RequestBody String payload" in the method, the Spring Framework will take the request body and set it to payload. If you set payload as a specific type. The framework will deserialize the body to it.
Finally, you can directly use payload in your code. The value of it would be the request body.
If you want to decode the json string. You can use org.json library.
JSONObject obj = new JSONObject(payload);
String name = obj.optString("name");
The code will get the value of name in the json.
I want to define a common exception manger in my project, so I use #ControllerAdvice to do, the snippet of code is below:
#ExceptionHandler(Exception.class)
public ModelAndView handleAllException(HttpServletRequest request, Exception ex) throws Exception
{
LOGGER.error(ex.getMessage());
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", request.getRequestURL());
mav.setViewName(ViewConstants.INTERNAL_ERROR_VIEW);
return mav;
}
it will return a common error page. That's great for normal exception of request. But if this is a Ajax request, the result is so ugly. so I add the code to judge it. The added code is below:
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
// return HTTP Status code and response message
} else {
// return error page name
}
I don't think it is the best way, anybody have a good opinion?
I have all my controllers in different packages based on whether they serve AJAX requests or not. Then I can set #basePackages element on the ControllerAdvice annotations to handle the exception accordingly
Update:
See RequestMapping#params and RequestMapping#headers to separate controllers based on headers and/or params
I would suggest to set error response code on any request, think this is a good practice to notify client that something goes wrong not depending on type of request. And for ajax request you can return same page and identify problem by error code.
If you use jQuery for making requests, you could use the following:
jQuery.ajaxSetup({
headers: { 'ajax-request': true },
statusCode: {
400: function (xhr) {
...do something
},
500: function (xhr) {
...do something
}
...
}
});
...
public class Application extends SpringBootServletInitializer {
#Bean(name = "simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
r.setDefaultErrorView("forward:/errorController");
return r;
}
#Controller
public class ErrorController {
public static final Logger LOG = Logger.getLogger(ErrorController.class);
#RequestMapping(value = "/errorController")
public ModelAndView handleError(HttpServletRequest request,
#RequestAttribute("exception") Throwable th) {
ModelAndView mv = null;
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
if (isBusinessException(th)) {
mv = new ModelAndView("appAjaxBadRequest");
mv.setStatus(BAD_REQUEST);
} else {
LOG.error("Internal server error while processing AJAX call.", th);
mv = new ModelAndView("appAjaxInternalServerError");
mv.setStatus(INTERNAL_SERVER_ERROR);
}
mv.addObject("message", getUserFriendlyErrorMessage(th).replaceAll("\r?\n", "<br/>"));
} else {
LOG.error("Cannot process http request.", th);
mv = new ModelAndView("appErrorPage");
mv.addObject("exeption", th);
}
return mv;
}
}
My problem is that I want to create an #ExceptionHandler method that will capture all un-handled exceptions. Once captured I would like to redirect to the current page instead of specifying a separate page just to display error.
Basically how do I get the value of someview returned by somemethod and set it dynamically in the method unhandledExceptionHandler below.
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex){
System.out.println("unhandle exception here!!!");
ModelAndView mv = new ModelAndView();
mv.setViewName("currentview");
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
So in JSP I can render this error message back into the current page something like that.
<c:if test="${not empty UNHANDLED_ERROR}">
<div class="messageError"> ${UNHANDLED_ERROR}</div>
</c:if>
I don't think there is way to do what you are asking for, because in the exception handler method unhandledExceptionHandler there is no way to find out what the name of the view that the handler method somemethod would have returned.
The only way is for you to introduce some sort of meta data scheme so that when you end up in the exception handler you can figure out what view to map it to. But I think this meta data scheme would be fairly complex. You can implement such a scheme by finding out what was the original url being accessed when the exception was thrown, this can be done with the code snippet below.
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
Once you know what the original request URL you can redirect to it, maybe using flash attribute to store the fact that there was an exception and what the error is.
The main problem wit the metadata will occur when you have a handler method that select between different views something like.
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
if(someCondition) {
abc.length();
return "someview";
} else {
// do some stuff here.
return "someOtherView";
}
}
Even knowing that somemethod was the source of the error leaves you not knowing which branch in the if statement caused the exception.
I dont think you can do this without modifying all of your handler methods. However you can try to do this in a "pretty" way:
1) You can define your own annotation which will accept target view name as a parameter (e.g. #ExceptionView)
2) Next thing to do is marking your handler methods with it, e.g.:
#ExceptionView("someview")
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
3) After that you can do something like this in exception handler:
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex, HandlerMethod hm) {
String targetView;
if (hm != null && hm.hasMethodAnnotation(ExceptionView.class)) {
targetView = hm.getMethodAnnotation(ExceptionView.class).getValue();
} else {
targetView = "someRedirectView"; // kind of a fallback
}
ModelAndView mv = new ModelAndView();
mv.setViewName(targetView);
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
Rather than sending the error on a separate page, you can you just put the error in the ModelAndView object. In your case you can just put the try/catch in your controller method and return the same view like so:
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(ModelAndView mv,HttpSession session) throws Exception {
mv.setViewName("someview");
try{
String abc = null;
abc.length();
} catch(Exception e) {
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
}
return mv;
}
So add the ModelAndView to your method and return it.
I have not tried this out, but based on the documentation here, we can get the request object in the exception handler. We may not be able to get the view linked to the URL. Getting the view from the URL, and the state/model of the view will be the tricky part.
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
Create a controller method annotated with #RequestMethod("/server-error")
Create a controller method annotated with #ExceptionHandler which will return "forward:/server-error";