Created
May 29, 2013 20:38
-
-
Save wvuong/5673644 to your computer and use it in GitHub Desktop.
Simple generic REST-ful Spring MVC controller, interops with Spring Data repositories
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Awesome sir. Kudo
thank you :)
Shouldn't there be ResourceNotFoundExceptions thrown when the entity doesn't exist whenever you do a repo.findOne?
Thank you! This stayed awesome! I economized much code!
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));
}}
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
Wily helpful, it will help me to solve a problem that i was facing for a while.