luca
luca

Reputation: 3328

Manage REST response with Spring MVC

I'm using Spring to create a REST api. At the moment I use this structure to provide a file explorer service:

FileModel.java

package hello;


    public class FileModel {
        private String name;
        private Long lastUpdate;
        private Long size;

        /**
         * Void Constructor
         */
        public FileModel() {
        }


        /**
         * Parametrized constructor
         * @param name
         * @param created
         * @param lastUpdate
         * @param size
         */
        public FileModel(String name, Long lastUpdate, Long size) {
            super();
            this.name = name;
            this.lastUpdate = lastUpdate;
            this.size = size;
        }


        /**
         * @return the name
         */
        public String getName() {
            return name;
        }


        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }


        /**
         * @return the lastUpdate:A long value representing the time the file was last modified, 
         * measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
         */
        public Long getLastUpdate() {
            return lastUpdate;
        }


        /**
         * @param lastUpdate the lastUpdate to set
         */
        public void setLastUpdate(Long lastUpdate) {
            this.lastUpdate = lastUpdate;
        }


        /**
         * @return the size in bytes
         */
        public Long getSize() {
            return size;
        }


        /**
         * @param size the size to set
         */
        public void setSize(Long size) {
            this.size = size;
        }


    }

FileServices.java

    package hello;

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.nio.file.Files;
    import java.nio.file.LinkOption;
    import java.util.ArrayList;

    import org.springframework.stereotype.Service;

    @Service
    public class FileServices {

        public ArrayList<FileModel> getAllFiles(String path) throws FileNotFoundException {
            ArrayList<FileModel> files=new ArrayList<FileModel>();
            File directory = new File(path);
            if (directory.exists()){
                //get all the files from a directory
                File[] fList = directory.listFiles();
                //check if list is null
                for (File file : fList){
                    if (file.isFile()){
                        FileModel f=new FileModel(file.getName(),file.lastModified(),file.length());
                        files.add(f);
                    }
                }
                return files;
            }
            else throw new ResourceNotFoundException(path);
        }
    }

ResourceNotFoundException.java

    package hello;

    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;

    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class ResourceNotFoundException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public ResourceNotFoundException(String path){
            super("The specified path: "+ path +"  doesn't exist");
        }
    }

FileManager

    package hello;

    import java.io.FileNotFoundException;
    import java.util.Collection;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class FileManager {

        @Autowired
        private FileServices file;

        @RequestMapping(value = "/files", method = RequestMethod.GET)
        public Collection<FileModel> getAllFiles(@RequestParam(value="path", defaultValue="/home") String path) throws FileNotFoundException {
            return file.getAllFiles(path);
        }

    }

The response is either error message like this

> {   "timestamp": 1442560477794,   "status": 404,   "error": "Not
> Found",   "exception": "hello.ResourceNotFoundException",   "message":
> "The specified path: /home  doesn't exist",   "path":
> "/MatlabLib/files" }

or this.

[
  {
    "name": "apache-tomcat-8.0.26-windows-x64.zip",
    "lastUpdate": 1441282759343,
    "size": 10470002
  },
  {
    "name": "desktop.ini",
    "lastUpdate": 1441357976196,
    "size": 282
  }
]

Given that I have to manage this web services through other java or matlab code, I need a custom response, for example with status, error, exception, message, body so I can check status code and undestand if there is an error or less. Is there in Spring a building method to do it?

Thanks

UPDATE: I created two response class, one for ErrorResponse and one for Response with different number of fied. Then I used

@ControllerAdvice 
public class ErrorController {

    /**
     * 
     * @param e: exception thrown
     * @return ErroreResponse
     */
    @ExceptionHandler(Exception.class)
    public @ResponseBody ErrorResponse errorHandler(Exception e){   
        //Make the exception by buildErrorResponse
        return ErrorResponseBuilder.buildErrorResponse(e);
    }

in the ErrorResponseBuilder made this method:

/**
 * Build exception response beginning from exception
 * @param e exception thrown
 * @return ErrorResponse: response of an exception
 */
public static ErrorResponse buildErrorResponse(Exception e){
    StringWriter errors = new StringWriter();
    e.printStackTrace(new PrintWriter(errors));
     return new ErrorResponse(HttpStatusManager.getHttpCode(e),e.getClass().getName(),e.getMessage(),errors.toString());
}

and in a HttpStatusManager I implemented this:

public HttpStatusManager() {
    }

    /**
     * Add to this class all new exception associating a code error
     * @param exception
     * @return
     */
    public static int getHttpCode(Exception exception){
        if (exception instanceof ResourceNotFoundException);
            return HttpStatus.NOT_FOUND.value();
        }

so from the controlle use this simple line

@RequestMapping(value = "/files", method = RequestMethod.GET)
    public Response<Collection<FileModel>> getAllFiles(@RequestParam(value="path", defaultValue="/home") String path) throws ResourceNotFoundException {
        Collection<FileModel> result;
        result = file.getAllFiles(path);
        return new Response<Collection<FileModel>>(HttpStatus.OK.value(),result);
    }

What do you think?

Upvotes: 1

Views: 3854

Answers (3)

pzeszko
pzeszko

Reputation: 2119

You could use javax.ws.rs.core.Response which has nice API. But personally, I would rather create a custom class to handle such responses.

Of course, you need to attach to your project f.e. Jackson JSON API so you could build methods which returns objects. Also you have to configure messageConverters in your spring configuration file.


UPDATE

public class Response {

private Object responseBody;
private String message;
private int responseCode;
public Response() {
    responseCode = 200; //default HTTP 200 OK
}
public Object getResponseBody() {
    return responseBody;
}

public void setResponseBody(Object responseBody) {
    this.responseBody = responseBody;
}

public String getMessage() {
    return message;
}

public void setMessage(String message) {
    this.message = message;
}

public int getResponseCode() {
    return responseCode;
}

public void setResponseCode(int responseCode) {
    this.responseCode = responseCode;
}


}

Example of usage

  @RequestMapping(value = "/files", method = RequestMethod.GET)
    public Response getAllFiles(@RequestParam(value="path", defaultValue="/home") String path) {
        Response response = new Response();
        try {
            Collection<FileModel> files = file.getAllFiles(path);
            response.setResponseBody(files);
        } catch (FileNotFoundException e) {
            Utils.setErrMessage(response, e);
        }

        return response;
    }

Your setErrMessage function may look like this

public void setErrMessage(Response response, Exception e) {
    if(e instanceof NullPointerException) {
        response.setErrCode(400); //HTTP 400 Bad Request
    }
    else if(e instanceof FileNotFoundException || ...) {
        response.setErrCode(500); //HTTP 500 Interval Server Error
    }
    ...
    response.setMessage(e.getMessage);
}

It's just a general idea, you can change it whatever you like.

Upvotes: 2

M4ver1k
M4ver1k

Reputation: 1575

I have a generic reponse DTO and I use Spring @ControllerAdvice and @ExceptionHandler to achieve a global error handling mechanism in my rest services.

Global Error Handler Code:

@ControllerAdvice    
public class WSControllerAdvice {
        private static Logger logger = Logger.getLogger(SearchControllerAdvice.class);
        @ExceptionHandler(Exception.class)
        public @ResponseBody WSResponse<String> errorHandler(Exception e){
            WSResponse<String> response = new WSResponse<String>();
            logger.error("Exception occured "+e.getMessage());
            response.setResponseStatus(WSResponseCode.UNHANDLED_EXCEPTION);
            response.setCount(0);
            response.getErrors().add("Unhandled Search Error : " + e.getMessage());
            return response;
        }
    }

Generic Response DTO

public class WSResponse<T> {

    private String responseStatus;
    private long count;
    private long totalNumFound;
    private T results ;
    private List<String> errors = new ArrayList<>();
    private String nextPageMark;
    private List<Facet> facets = new ArrayList<>();
}

Getter Setter removed

You have a consistent response format with this structure in place,irrespective of the fact that an exception occurs or not.

Upvotes: 1

Karthik R
Karthik R

Reputation: 5786

There are two ways to achieve this:

Upvotes: 0

Related Questions