Handling MaxUploadSizeExceededException with Spring MVC - spring-mvc

How can I intercept and send custom error messages with file upload when file size is exceeded. I have an annotated exception handler in the controller class, but the request does not come to the controller. The answer I came across on this link How to handle MaxUploadSizeExceededException suggests implementing HandlerExceptionResolver.
Have things changed in Spring 3.5 or is that still the only solution?

I ended up implementing HandlerExceptionResolver:
#Component public class ExceptionResolverImpl implements HandlerExceptionResolver {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionResolverImpl.class);
#Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object obj, Exception exc) {
if(exc instanceof MaxUploadSizeExceededException) {
response.setContentType("text/html");
response.setStatus(HttpStatus.REQUEST_ENTITY_TOO_LARGE.value());
try {
PrintWriter out = response.getWriter();
Long maxSizeInBytes = ((MaxUploadSizeExceededException) exc).getMaxUploadSize();
String message = "Maximum upload size of " + maxSizeInBytes + " Bytes per attachment exceeded";
//send json response
JSONObject json = new JSONObject();
json.put(REConstants.JSON_KEY_MESSAGE, message);
json.put(REConstants.JSON_KEY_SUCCESS, false);
String body = json.toString();
out.println("<html><body><textarea>" + body + "</textarea></body></html>");
return new ModelAndView();
}
catch (IOException e) {
LOG.error("Error writing to output stream", e);
}
}
//for default behaviour
return null;
}
}

Related

How to read HTTP 500 using a Spring RestTemplate client

A simple Spring Boot REST Controller
#PostMapping(path = "check-and-submit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<MyOutput> checkAndSave(#RequestBody #Valid MyInput input, Errors errors){
ResponseEntity<MyOutput> result = null;
if (errors.hasErrors()) {
result = new ResponseEntity<>(MyOutput.buildErrorResponse(errors), HttpStatus.INTERNAL_SERVER_ERROR);
} else {
myDao.save(input.buildEntity());
result = new ResponseEntity<>(MyOutput.buildSuccessResponse(), HttpStatus.OK);
}
return result;
}
And the test class for it
public static void main(String[] args) {
MyInput dto = new MyInput();
// set properties
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Content-Type", "application/json");
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<MyInput> request = new HttpEntity<MyInput>(dto, headers);
try {
ResponseEntity<MyOutput> result = restTemplate.postForEntity(URL, request, MyOutput.class);
System.out.println(result);
} catch(Exception e) {
e.printStackTrace();
}
}
For success scenario this works fine. But, for exception scenrio, i.e. HTTP 500 this fails
org.springframework.web.client.HttpServerErrorException: 500 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:97)
As suggested in one of the posts, I created a error-handler that can successfully read the response
public class TestHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
Scanner scanner = new Scanner(response.getBody());
String data = "";
while (scanner.hasNext())
data += scanner.next();
System.out.println(data);
scanner.close();
}
}
But how can I let RestTemplate read and deserialize the response JSON even in case of HTTP 500.
Before any other human-question-flagging-bot marks this as duplicate, here's a humble explanation on how this is different from the others.
All other questions address how to handle HTTP 500, at max read the response-body. This questions is directed at if it is possible to deserialize the response as JSON as well. Such functionality is well established in frameworks such as JBoss RESTEasy. Checking how same can be achieved in Spring.
This should work.
try {
ResponseEntity<MyOutput> result = restTemplate.postForEntity(URL, request, MyOutput.class);
} catch(HttpServerErrorException errorException) {
String responseBody = errorException.getResponseBodyAsString();
// You can use this string to create MyOutput pojo using ObjectMapper.
}

Exception handling in spring mvc

Which is the best way to capture a exception in spring mvc. I am not getting the end to end implementation of the exception handling in spring mvc.
I have implemented #ControllerAdvice. Can you please confirm whether I have implemented in right way to handle the exception.
Question:
How can I handle the service layer exception. Do I have to throw it to controller and then to UI? How it works.
How I can handle the sql exception in DAO layer and other exception like Numberformat exception?
Code:
#RequestMapping(value = "/getDepositSearch", method = RequestMethod.POST)
public String depositNumberData(
#ModelAttribute("searchCondition") String searchCondition,
#ModelAttribute("searchText") String searchText,
final RedirectAttributes redirect, Model depositStatus,
HttpServletRequest request) {
String pageForward = null;
try {
List<MRPSDeposit> depositDetails = null;
if (!searchText.isEmpty()) {
depositDetails = mrpsDeposit.getDepositDetails(searchCondition,
searchText);
}
Map<String, String> searchList = new LinkedHashMap<String, String>();
if (searchCondition.equals(ManagementConstants.DEPOSITDATEKEY)) {
searchList.put(ManagementConstants.DEPOSITDATEKEY,
ManagementConstants.DEPOSITDATEVALUE);
} else if (searchCondition.equals(ManagementConstants.DEPOSITNUMBERKEY)) {
searchList.put(ManagementConstants.DEPOSITNUMBERKEY,
ManagementConstants.DEPOSITNUMBERVALUE);
} else {
searchList.put(ManagementConstants.DEPOSITNUMBERKEY,
ManagementConstants.DEPOSITNUMBERVALUE);
searchList.put(ManagementConstants.DEPOSITDATEKEY,
ManagementConstants.DEPOSITDATEVALUE);
}
if (depositDetails.size() == 0) {
redirect.addFlashAttribute("flashMessage",
ManagementConstants.NORECORDFOUND);
pageForward = "redirect:/mrps/getDepositDetails";
} else if (depositDetails.size() > 1) {
Map<String, Map<String, String>> search = new HashMap<String, Map<String, String>>();
search.put("searchContent", searchList);
depositStatus.addAttribute("searchAllContents", search);
depositStatus.addAttribute("depositDetails", depositDetails);
pageForward = "multipleDepositDetails";
} else {
Map<String, Map<String, String>> search = new HashMap<String, Map<String, String>>();
search.put("searchContent", searchList);
depositStatus.addAttribute("searchAllContents", search);
depositStatus.addAttribute("depositDetails", depositDetails);
if (request.isUserInRole("ROLE_READ")) {
pageForward = "readDepositDetails";
} else {
pageForward = "updateDepositDetails";
}
}
} catch (InfoManagementException e) {
System.out.println("weee"+e);
}
return pageForward;
}
Service layer:
#Override
#Transactional(readOnly = true)
public List<MRPSDeposit> getDepositDetails(String searchCondition,
String searchText) {
List<MRPSDeposit> mrpsDepositDetails = new ArrayList<MRPSDeposit>();
/* try { */
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd",
Locale.ENGLISH);
if (searchCondition.equalsIgnoreCase(ManagementConstants.DEPOSITNUMBERKEY)) {
System.out.println("finalal");
mrpsDepositDetails = mrpsDepositDao.findByDepositNumber(
searchCondition, Short.valueOf(searchText));
} else {
try {
mrpsDepositDetails = mrpsDepositDao.findByDepositDate(
searchCondition, formatter.parse(searchText));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return mrpsDepositDetails;
}
DAO layer:
#Override
public List<MRPSDeposit> findByDepositNumber(String searchCondition,
Short searchTxt) {
List<MRPSDeposit> searchResult = super.findByDepositNumber(
searchCondition, searchTxt);
return searchResult;
}
Controller Advice:
#ControllerAdvice
public class GlobalExceptionController {
#ExceptionHandler(InfoManagementException.class)
public ModelAndView handleCustomException(InfoManagementException ex) {
ModelAndView model = new ModelAndView("error/generic_error");
System.out.println();
model.addObject("errCode", ex.getErrCode());
model.addObject("errMsg", ex.getErrMsg());
return model;
}
#ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
ModelAndView model = new ModelAndView("error/generic_error");
model.addObject("errMsg", "this is Exception.class");
return model;
}
}
How can I handle the service layer exception. Do I have to throw it to
controller and then to UI? How it works.
In both cases that depends on what are your requirements. There are cases when you will need to show the same page with an error message, also there are some cases when you need to redirect to another error page. In other cases probably you don't need to show any error message.
It's common to throw the exception to the controller then process it in the controller advice and show an readable error message on the UI. In the controller advice you can determine the page that will show the message and also log the error message.
How I can handle the sql
exception in DAO layer and other exception like Numberformat
exception?
I would recommed you to use input validation in the controller. If you use it, then you won't get this kind of error. But if you don't have input validation you can throw the exception and show a message on the UI.
Update
You can leave your service layer as you have at this moment and process the exception in the ControllerAdvice. If you want to process the exception in the service layer you can do this with a try/catch.
public void myServiceMethod(){
try{
...
}catch(Exception1 e){//Every catch block can capture a group of exceptions.
//Depending on your business logic, you can throw a new Exception, log it, or do some logic.
logger.log("My error: ", e);
}catch(Exception2 e){//Every catch block can capture a group of exceptions.
throw new MyBusinessException("Something ocurred", e);
}
}
Then in your ControllerAdvice you need to process MyBusinessException and do what you need.

How to send http request parameters using Jersey client

I use the following rest client implementation of jersey to consume a rest service.I am able to do it successfully.Additionally now I need to send request parameters which will be consumed as part of HttpServletRequest on the producer side.
Consumer side Jersey client code
private ClientResponse getWebClientResponse(String RESOURCE_PATH, String methodType, Object requestObj) {
WebResource webResource;
ClientResponse response = null;
try {
String environmentHost = EnvironmentUtil.resolveEnvironmentHost();
Client client = prepareClient();
String RWP_BASE_URI = environmentHost + "/workflow/rest";
webResource = client.resource(RWP_BASE_URI);
WebResource path = webResource.path(RESOURCE_PATH);
if (GET.equals(methodType)) {
response = path.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(
ClientResponse.class);
} else if (POST.equalsIgnoreCase(methodType)) {
response = path.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(ClientResponse.class, requestObj);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
return response;
}
Producer side
#Context
public void setContext(SecurityContext context) {
this.context = context;
}
public HttpServletRequest getRequest() {
return request;
}
#Context
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public String getSessionUserPID(final HttpServletRequest request,
final SecurityContext context) {
if (request.getSession(false) == null) {
final String exceptionMessage = "getSessionUserPID() failed, session NOT FOUND for this request";
final Response response = Response.status(ExceptionStatus.UNAUTHORIZED.getNumber())
.entity(exceptionMessage).build();
LOG.error(exceptionMessage);
throw new WebApplicationException(response);
}
if (context.getUserPrincipal() == null) {
final String exceptionMessage = "getSessionUserPID() failed, user principal NOT FOUND";
final Response response = Response.status(ExceptionStatus.UNAUTHORIZED.getNumber())
.entity(exceptionMessage).build();
LOG.error(exceptionMessage);
throw new WebApplicationException(response);
}
final String userPID = context.getUserPrincipal().getName();
if (userPID == null || userPID.isEmpty()) {
final String exceptionMessage = "getSessionUserPID() failed, user principal name cannot be null or empty";
final Response response = Response.status(ExceptionStatus.UNAUTHORIZED.getNumber())
.entity(exceptionMessage).build();
LOG.error(exceptionMessage);
throw new WebApplicationException(response);
}
return userPID;
}
The main intention here is currently I get user information from weblogic security context but for a particular scenario I need to pass this part of rest service request and obtain it from HttpServletRequest object.How can I obtain this from httpservletrequest
You can use QueryParam or PathParam in GET method and FormParam in the POST method for sending request parameter to the server.

Jersey2 Client reuse not working AsyncInvoker

I am trying to reuse a Jersey2(Jersey 2.16) Client for async invocation. However after 2 requests, I see that the threads going into a waiting state, waiting on a lock. Since client creation is an expensive operation, I am trying to reuse the client in the async calls. The issue occurs only with ApacheConnectorProvider as the connector class. I want to use ApacheConnectorProvider, as I need to use a proxy and set SSL properties and I want to use PoolingHttpClientConnectionManager.
The sample code is given below:
public class Example {
Integer eventId = 0;
private ClientConfig getClientConfig()
{
ClientConfig clientConfig = new ClientConfig();
ApacheConnectorProvider provider = new ApacheConnectorProvider();
clientConfig.property(ClientProperties.REQUEST_ENTITY_PROCESSING,RequestEntityProcessing.BUFFERED);
clientConfig.connectorProvider(provider);
return clientConfig;
}
private Client createClient()
{
Client client = ClientBuilder.newClient(getClientConfig());
return client;
}
public void testAsyncCall()
{
Client client = createClient();
System.out.println("Testing a new Async call on thread " + Thread.currentThread().getId());
JSONObject jsonObject = new JSONObject();
jsonObject.put("value", eventId++);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
client.close();
}
private void invoker(Client client, String URI, JSONObject jsonObject)
{
final Future<Response> responseFuture = client.target(URI)
.request()
.async()
.post(Entity.entity(jsonObject.toJSONString(), MediaType.TEXT_PLAIN));
try {
Response r = responseFuture.get();
System.out.println("Response is on URI " + URI + " : " + r.getStatus());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[])
{
Example client1 = new Example();
client1.testAsyncCall();
return;
}
}
The response I see is:
Testing a new Async call on thread 1
Response is on URI http://requestb.in/nn0sffnn : 200
Response is on URI http://requestb.in/nn0sffnn : 200
On looking at the thread stack, I see the following trace:
"jersey-client-async-executor-0" prio=6 tid=0x043a4c00 nid=0x56f0 waiting on condition [0x03e5f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x238ee148> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:133)
at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:282)
at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:64)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:177)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:170)
Can someone give me a suggestion as to how to reuse Client objects for async requests and may be how to get over this issue as well.

How to handle timeout of AsynchronousResponse object in RestEasy

i am implementing a jax-rs service with RestEasy on JBoss AS 7.1.2 an i would like to use asynchronous HTTP processsing as described here: http://docs.jboss.org/resteasy/docs/1.0.0.GA/userguide/html/Asynchronous_HTTP_Request_Processing.html
For thr AsynchronousResponse I define a timeout of 10 seconds. When this period expires, the request is responded with a 200 OK and an empty body. I would like to modify this behaviour so i need to be notified about the timeout event.
In my solution, I would like to handle the timeout event in a NotificationManager object, which keeps the AsycnhronousResponse for the time being. Please see the code below for details.
So far, i could not figure out how to do that. Does anyone have more experience with the RestEasy Asynchronous HTTP processing?
#POST
#Path("/blabla")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void subscribeLongPolling (
final #Suspend(10000) AsynchronousResponse response,
JAXBElement<LongPollingRequestParameters> rqParam,
#Context HttpServletRequest req) throws Exception {
//do some stuff with req
Thread t = new Thread("ThreadSubscribeTo:" + channelID)
{
#Override
public void run() {
//hand over to Notification Manager to return notifications in case some exist
try {
NotificationManager nm = new NotificationManager();
nm.setAsyncResponseObject(response);
logger.info("Response object registered in NotificationManager");
}catch (Exception e){
e.printStackTrace();
}
}
};
t.start();
logger.info("Releasing Thread");
}
public class NotificationManager {
private AsynchronousResponse response;
private NotificationList nList;
public synchronized void setAsyncResponseObject(AsynchronousResponse response) {
this.response = response;
if (nList.getAny().size() > 0) {
logger.info("Stored notification send to web client: " + nList.getAny().get(0).toString());
sendNotification(nList.getAny().remove(0));
}
}
public synchronized void sendNotification(Object message){
if (response != null){
logger.info("Response object found. Send notification immediately: " + message.toString());
Response responseObject = Response.ok(message, MediaType.APPLICATION_XML).build();
response.setResponse(responseObject);
response = null;
}else{
logger.info("Response object not found notification will be stored");
addNotification(message);
}
}
}
Thanks in advance,
Alex

Resources