Skip to content

Instantly share code, notes, and snippets.

@wvuong
Created May 29, 2013 20:38
Show Gist options
  • Save wvuong/5673644 to your computer and use it in GitHub Desktop.
Save wvuong/5673644 to your computer and use it in GitHub Desktop.
Simple generic REST-ful Spring MVC controller, interops with Spring Data repositories
package com.willvuong.foodie.controller;
import com.willvuong.foodie.dao.PlaceRepository;
import com.willvuong.foodie.domain.Place;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/rest/places")
public class PlacesRESTController extends RESTController<Place, String> {
@Autowired
public PlacesRESTController(PlaceRepository repo) {
super(repo);
}
}
package com.willvuong.foodie.controller;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
public abstract class RESTController<T, ID extends Serializable> {
private Logger logger = LoggerFactory.getLogger(RESTController.class);
private CrudRepository<T, ID> repo;
public RESTController(CrudRepository<T, ID> repo) {
this.repo = repo;
}
@RequestMapping
public @ResponseBody List<T> listAll() {
Iterable<T> all = this.repo.findAll();
return Lists.newArrayList(all);
}
@RequestMapping(method=RequestMethod.POST, consumes={MediaType.APPLICATION_JSON_VALUE})
public @ResponseBody Map<String, Object> create(@RequestBody T json) {
logger.debug("create() with body {} of type {}", json, json.getClass());
T created = this.repo.save(json);
Map<String, Object> m = Maps.newHashMap();
m.put("success", true);
m.put("created", created);
return m;
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public @ResponseBody T get(@PathVariable ID id) {
return this.repo.findOne(id);
}
@RequestMapping(value="/{id}", method=RequestMethod.POST, consumes={MediaType.APPLICATION_JSON_VALUE})
public @ResponseBody Map<String, Object> update(@PathVariable ID id, @RequestBody T json) {
logger.debug("update() of id#{} with body {}", id, json);
logger.debug("T json is of type {}", json.getClass());
T entity = this.repo.findOne(id);
try {
BeanUtils.copyProperties(entity, json);
}
catch (Exception e) {
logger.warn("while copying properties", e);
throw Throwables.propagate(e);
}
logger.debug("merged entity: {}", entity);
T updated = this.repo.save(entity);
logger.debug("updated enitity: {}", updated);
Map<String, Object> m = Maps.newHashMap();
m.put("success", true);
m.put("id", id);
m.put("updated", updated);
return m;
}
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public @ResponseBody Map<String, Object> delete(@PathVariable ID id) {
this.repo.delete(id);
Map<String, Object> m = Maps.newHashMap();
m.put("success", true);
return m;
}
}
@netodevel
Copy link

RESTController<Place, String> ? ID String?

@VinodChilukuru
Copy link

Hi wvuong, Thanks for sharing this, really useful for me as I am also implementing the similar thing in my project. Could you please share the entire code?

@edifortcarlos
Copy link

Wily helpful, it will help me to solve a problem that i was facing for a while.

@nhdinh
Copy link

nhdinh commented Oct 24, 2017

Awesome sir. Kudo

@ImanMousavi
Copy link

thank you :)

@derylspielman
Copy link

Shouldn't there be ResourceNotFoundExceptions thrown when the entity doesn't exist whenever you do a repo.findOne?

@filipeovercom
Copy link

Thank you! This stayed awesome! I economized much code!

@hghammoud
Copy link

hghammoud commented Aug 20, 2018

Thanks for the sharing. @ small erroes maybe related the the version of the libraries your using.

if using BeanUtils of spring, the copy order is reversed. its

BeanUtils.copyProperties(json, entity)

findOne might be replaced with findById.

Example with reactive streams

...   
import io.micrometer.core.annotation.Timed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.Serializable;

public abstract class GenericRestController<T, ID extends Serializable> {
private final Logger log = LoggerFactory.getLogger(VenueController.class);

protected ReactiveMongoRepository<T, ID> repo;

public GenericRestController(ReactiveMongoRepository<T, ID> repo) {
    this.repo = repo;
}

@GetMapping
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public Flux<T> listAll() {
    return repo.findAll();
}

@PostMapping
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public Mono<ResponseEntity<T>> create(@RequestBody T entry) {
    return repo.save(entry)
            .map(o -> new ResponseEntity<T>(o, HttpStatus.CREATED))
            .onErrorMap(err -> {
                log.error("Error occured while creating entity", err);
                return new InternalServerErrorException("Error occured while creating entity", ErrorConstants.ERR_CREATION);
            });
}

@GetMapping(value = "/{id}")
@Timed
@Secured(AuthoritiesConstants.USER)
public Mono<ResponseEntity<T>> get(@PathVariable ID id) {
    return repo.findById(id)
            .map(o -> new ResponseEntity<>(o, HttpStatus.OK))
            .switchIfEmpty(Mono.error(new EntryNotFoundException("Entry not found with id " + id, "")))
            .onErrorMap(err -> {
                log.error("Error occured while fetching entity", err);
                return new InternalServerErrorException("Error occured while fetching entity", ErrorConstants.ERR_FETCHING);
            });
}

@PutMapping(value = "/{id}")
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public Mono<ResponseEntity<T>> update(@PathVariable ID id, @RequestBody T json) {
    return repo.findById(id)
            .switchIfEmpty(Mono.error(new EntryNotFoundException("Entry not found with id " + id + ".No update was done.", "")))
            .onErrorMap(err -> {
                log.error("Error occured while fetching entity", err);
                return new InternalServerErrorException("Error occured while fetching entity", ErrorConstants.ERR_FETCHING);
            })
            .doOnNext(entity -> BeanUtils.copyProperties(json, entity))
            .flatMap(e -> repo.save(e))
            .map(saved -> new ResponseEntity<T>(saved, HttpStatus.OK));
}

@DeleteMapping(value = "/{id}")
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public Mono<ResponseEntity> delete(@PathVariable ID id) {
    return repo.findById(id)
            .switchIfEmpty(Mono.error(new EntryNotFoundException("Entry not found with id " + id, "")))
            .flatMap(o -> repo.deleteById(id))
            .map((r) -> new ResponseEntity(HttpStatus.NO_CONTENT));
}}

@renanleandrof
Copy link

This only work if ID is String.
Spring can't deduce the type of @PathVariable ID id

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment