package demo; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.domain.Sort; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.sync.diffsync.PersistenceCallback; import org.springframework.sync.diffsync.PersistenceCallbackRegistry; import org.springframework.sync.diffsync.config.DiffSyncConfigurerAdapter; import org.springframework.sync.diffsync.config.EnableDifferentialSynchronization; import org.springframework.sync.diffsync.web.JsonPatchHttpMessageConverter; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.filter.ShallowEtagHeaderFilter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.io.IOException; import java.io.Serializable; import java.util.List; /** * This is the code stolen _directly_ from my * <a href="https://spring.io/blog/2014/10/08/streaming-json-patch-from-spring-to-a-react-ui"> * amazing colleagues' Spring Sync example available and explained here</a>. * * @author Roy Clarkson * @author Craig Walls * @author Greg L. Turnquist */ @Configuration @ComponentScan @EnableAutoConfiguration public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean CommandLineRunner commandLineRunner(TodoRepository repository) { return args -> { repository.save(new Todo(1L, "a", false)); repository.save(new Todo(2L, "b", false)); repository.save(new Todo(3L, "c", false)); }; } } @Configuration class WebConfig extends WebMvcConfigurerAdapter { @Bean public ShallowEtagHeaderFilter etagFilter() { return new ShallowEtagHeaderFilter(); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new JsonPatchHttpMessageConverter()); } } @Configuration @EnableDifferentialSynchronization class DiffSyncConfig extends DiffSyncConfigurerAdapter { @Autowired private PagingAndSortingRepository<Todo, Long> repo; @Override public void addPersistenceCallbacks(PersistenceCallbackRegistry registry) { registry.addPersistenceCallback(new JpaPersistenceCallback<Todo>(repo, Todo.class)); } } class JpaPersistenceCallback<T> implements PersistenceCallback<T> { private final PagingAndSortingRepository<T, Long> repo; private Class<T> entityType; public JpaPersistenceCallback(PagingAndSortingRepository<T, Long> repo, Class<T> entityType) { this.repo = repo; this.entityType = entityType; } @Override public List<T> findAll() { return (List<T>) repo.findAll(new Sort("id")); } @Override public T findOne(String id) { return repo.findOne(Long.valueOf(id)); } @Override public void persistChange(T itemToSave) { repo.save(itemToSave); } @Override public void persistChanges(List<T> itemsToSave, List<T> itemsToDelete) { repo.save(itemsToSave); repo.delete(itemsToDelete); } @Override public Class<T> getEntityType() { return entityType; } } @RestController @RequestMapping("/todos") class TodoController { @Autowired private TodoRepository repository; @RequestMapping(method = RequestMethod.GET, produces = "application/json") ResponseEntity<Iterable<Todo>> list() { HttpHeaders headers = new HttpHeaders(); headers.add("Accept-Patch", "application/json-patch+json"); return new ResponseEntity<Iterable<Todo>>( repository.findAll(), headers, HttpStatus.OK); } @RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json") Todo create(@RequestBody Todo todo) { return repository.save(todo); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = "application/json") @ResponseStatus(HttpStatus.NO_CONTENT) @Transactional void update(@RequestBody Todo updatedTodo, @PathVariable("id") long id) throws IOException { if (id != updatedTodo.getId()) { repository.delete(id); } repository.save(updatedTodo); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.NO_CONTENT) void delete(@PathVariable("id") long id) { repository.delete(id); } } interface TodoRepository extends PagingAndSortingRepository<Todo, Long> { } @Entity class Todo implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String description; private boolean complete; public void setId(Long id) { this.id = id; } public Long getId() { return id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isComplete() { return complete; } public void setComplete(boolean complete) { this.complete = complete; } public Todo() { } public Todo(Long id, String description, boolean complete) { this.id = id; this.description = description; this.complete = complete; } @Override public String toString() { return "[ id=" + this.id + ", description=" + this.description + ", complete=" + this.complete + " ]"; } @Override public boolean equals(Object other) { return EqualsBuilder.reflectionEquals(this, other); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } }