Link to home
Start Free TrialLog in
Avatar of Rohit Bajaj
Rohit BajajFlag for India

asked on

Best practice to handle exception within controllers spring mvc

Hi,
Inside the controller i am using DB operations. There is a separate class for Dao for performing it. But it may throw exceptions like : EmptyResultDataAccessException
Since this DB calls are made from within several methods inside the controller. Writing try catch for them doesnt work.
so i can put try/catch inside the dao class itself. But then i need to return proper response code and error message.
what are the possible ways to do it.

One way i found is  to extend my controller with a basecontroller having the following code :

public class BaseController {

    @ExceptionHandler(AppException.class)
    public void handleServerException(HttpServletRequest request, HttpServletResponse response, AppException e){
        try{
            String msg = "Page not found";
            response.getWriter().write(msg);
            response.setStatus(e.getStatusCode());
            response.setContentType(MediaType.TEXT_HTML.toString());

        }catch(IOException ioe){
            //TODO :            logger.error("Unable to process error",ioe);

        }
    }

Open in new window


Then within try/catch i use catch (EmptyResultDataAccessException e) {
            throw new AppException(e.getMessage(), 404, "Not Found");
        }


Please comment on this approach. Are there better ways ?

Thanks
Avatar of mccarl
mccarl
Flag of Australia image

I would say that using the @ExceptionHandler annotated method should be the most appropriate, however, I would have that method directly handle the EmptyResultDataAccessException (rather than catching it yourself and just throwing a different Exception). That way you have even less common code in your main controller methods.
Avatar of Rohit Bajaj

ASKER

Hi,
The exact code which is throwing exception is :

 @Override
    public Snippet getSnippetById(String id) {
        String query = "select id,title,mode,text from snippets where id=?";
        Snippet snippet;
        try {
            snippet = jdbcTemplate.queryForObject(query, new Object[]{id}, new SnippetMaper());
        } catch (EmptyResultDataAccessException e) {
            throw new AppException(e.getMessage(), 404, "Page not found");
        }
        catch (RuntimeException re) {
            throw new AppException(re.getMessage(),500,"Internal Server Error");
        }
        return snippet;
    }

Open in new window


So basically i need to handle two exceptions. That way i will need to write two exception handlers one for Empty Exception and other for RunTimeException ?
ASKER CERTIFIED SOLUTION
Avatar of mccarl
mccarl
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I am using             <spring.version>4.2.1.RELEASE</spring.version>
I would say that using the @ExceptionHandler annotated method should be the most appropriate, however, I would have that method directly handle the EmptyResultDataAccessException (rather than catching it yourself and just throwing a different Exception). That way you have even less common code in your main controller methods.


I second that.
Ok, if you can post your full code for your controller(s) I can give you some pointers.

But as a start, your exception handler method should work like this...

public class BaseController {

    @ExceptionHandler(EmptyResultDataAccessException.class)
    public ResponseEntity<String> handleServerException(EmptyResultDataAccessException e){
        return new ResponseEntity<String>("Page not found", HttpStatus.NOT_FOUND);
    }

Open in new window

Thanks. I am posting the full code :
SnippetController :
package org.directi.code.controller;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.directi.code.dao.SnippetDao;
import org.directi.code.model.Snippet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@Controller
public class SnippetController extends BaseController {

    @Autowired
    SnippetDao snippetDao;

    private static final Logger logger = Logger.getLogger(SnippetController.class);

    @RequestMapping(value = "/")
    public String newSnippet(HttpServletResponse response) throws IOException {
        return "new";
    }

    @RequestMapping(value = "/snippets", method = RequestMethod.POST)
    public void createSnippet(@RequestBody String jsonString, RedirectAttributes redirectAttributes, HttpServletResponse response, HttpServletRequest request) throws IOException {
        Snippet snippet = new ObjectMapper().readValue(jsonString, Snippet.class);
        String uuid = snippetDao.insertData(snippet);
        response.addHeader("Location", "http://" + request.getHeader("Host") + "/snippets/" + uuid);
    }

    @RequestMapping(value = "/snippets/{id}", method = RequestMethod.GET)
    public ModelAndView showSnippet(@PathVariable String id) {
        Snippet snippet = snippetDao.getSnippetById(id);
        String mode = snippet.getMode();
        ObjectMapper objectMapper = new ObjectMapper();
        String extraScripts = "";

        try {
            byte[] jsonData = Files.readAllBytes(Paths.get("utils/meta.json"));
            JsonNode rootNode = objectMapper.readTree(jsonData);
            JsonNode deps = rootNode.path("deps");
            JsonNode modeString = deps.path(mode);
            String jsFileList = modeString.getTextValue();
            String[] jsFileNames = jsFileList.split(",");
            for (int i = 0; i < jsFileNames.length; i++) {
                extraScripts += "<script src=\"/resources/lib/codemirror" + jsFileNames[i] + "\"></script>\n";
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        ModelAndView mav = new ModelAndView("snippet");
        snippet.setText(StringUtils.replaceEach(snippet.getText(), new String[]{"&", "\"", "<", ">"}, new String[]{"&amp;", "&quot;", "&lt;", "&gt;"}));
        mav.addObject("snippet", snippet);
        mav.addObject("extraScripts", extraScripts);
        return mav;
    }

    @RequestMapping(value = "/snippets/{id}", method = RequestMethod.PUT)
    public void showSnippet(@RequestBody String jsonString, @PathVariable String id, HttpServletResponse response) {
        try {
            Snippet snippet = new ObjectMapper().readValue(jsonString, Snippet.class);
            snippet.setId(id);
            snippetDao.updateData(snippet);
            response.setStatus(200);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/snippets/{id}/edit", method = RequestMethod.GET)
    public ModelAndView editSnippet(@PathVariable String id) {
        Snippet snippet = snippetDao.getSnippetById(id);
        snippet.setText(StringUtils.replaceEach(snippet.getText(), new String[]{"&", "\"", "<", ">"}, new String[]{"&amp;", "&quot;", "&lt;", "&gt;"}));
        ModelAndView mav = new ModelAndView("edit");
        mav.addObject("snippet", snippet);
        return mav;
    }

    @RequestMapping(value = "/snippets/{id}/raw", method = RequestMethod.GET)
    @ResponseBody
    public String rawSnippet(@PathVariable String id, HttpServletResponse response) {
        Snippet snippet = snippetDao.getSnippetById(id);
        String filename = snippetFilename(snippet.getTitle(), snippet.getMode());
        response.setHeader("Content-Type", "text/plain");
        response.setHeader("Content-Disposition", "inline; filename=\"" + filename + "\"");
        return snippet.getText();
    }

    private String snippetFilename(String title, String mode) {

        String filename = title.replaceAll("\\s", "_").replaceAll("\\W", "");
        ObjectMapper objectMapper = new ObjectMapper();
        String extension = "";
        try {
            byte[] jsonData = Files.readAllBytes(Paths.get("utils/meta.json"));
            JsonNode rootNode = objectMapper.readTree(jsonData);
            JsonNode extensions = rootNode.path("extensions");
            JsonNode extensionNode = extensions.path(mode);
            extension = extensionNode.getTextValue();

        } catch (IOException e) {
            e.printStackTrace();
        }
        filename += "." + extension;
        return filename;
    }
}

Open in new window


BaseController.java :

package org.directi.code.controller;


import org.apache.log4j.Logger;
import org.directi.code.exception.AppException;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public abstract class BaseController {
    private static final Logger logger  = Logger.getLogger(BaseController.class);

    @ExceptionHandler(AppException.class)
    public void handleServerException(HttpServletRequest request, HttpServletResponse response, AppException e){
        try{
            response.getWriter().write(e.getErrMessage());
            response.setStatus(e.getStatusCode());
            response.setContentType(MediaType.TEXT_HTML.toString());

        }catch(IOException ioe){
            logger.error("Unable to process error",ioe);

        }
    }
}

Open in new window


SnippetDaoImpl.java
package org.directi.code.dao;

import org.apache.log4j.Logger;
import org.directi.code.model.Snippet;
import org.directi.code.exception.AppException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;

@Component
public class SnippetDaoImpl implements SnippetDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    private static final Logger logger = Logger.getLogger(SnippetDaoImpl.class);

    public void setJdbcTemplate(JdbcTemplate template) {
        this.jdbcTemplate = template;
    }

    @Override
    public String insertData(Snippet snippet) {
        UUID uuid = UUID.randomUUID();
        String randomUUIDString = uuid.toString();
        snippet.setId(randomUUIDString);
        jdbcTemplate.update("INSERT INTO snippets (id, title, mode, text) VALUES (?, ?, ?, ?)", snippet.getId(), snippet.getTitle(), snippet.getMode(), snippet.getText());
        return randomUUIDString;
    }

    @Override
    public void updateData(Snippet snippet) {
        jdbcTemplate.update("UPDATE snippets set text = ? where id = ?", snippet.getText(), snippet.getId());
    }

    @Override
    public Snippet getSnippetById(String id) {
        String query = "select id,title,mode,text from snippets where id=?";
        Snippet snippet;
        try {
            snippet = jdbcTemplate.queryForObject(query, new Object[]{id}, new SnippetMaper());
        } catch (EmptyResultDataAccessException e) {
            logger.error("No records found",e);
            throw new AppException(e.getMessage(), 404, "Page not found");
        }
        catch (RuntimeException e) {
            logger.error("Error retrieving data from db",e);
            throw new AppException(e.getMessage(),500,"Internal Server Error");
        }
        return snippet;
    }

    private static final class SnippetMaper implements RowMapper<Snippet> {
        public Snippet mapRow(ResultSet rs, int rowNum) throws SQLException {
            Snippet snippet = new Snippet();
            snippet.setText(rs.getString("text"));
            snippet.setId(rs.getString("id"));
            snippet.setMode(rs.getString("mode"));
            snippet.setTitle(rs.getString("title"));
            return snippet;
        }
    }
}

Open in new window

Ok, your controller code doesn't look too bad at the moment. I think if you try an implement the things that I have mentioned (across a number of your questions) it would look pretty clean. The things I am talking about are.... using Spring automatic RequestBody conversion using Jackson, moving the meta.json data handling to a separate class, and using ResponseEntity rather than dealing directly with the HttpServletResponse object.