diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java index bde1f5a5f..ab695aeeb 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,7 +143,7 @@ class AbstractRepositoryRestController { headers = ETag.from(resource).addTo(headers); // Add Last-Modified - AuditableBeanWrapper wrapper = auditableBeanWrapperFactory.getBeanWrapperFor(resource.getContent()); + AuditableBeanWrapper wrapper = getAuditableBeanWrapper(resource.getContent()); if (wrapper == null) { return headers; @@ -157,4 +157,14 @@ class AbstractRepositoryRestController { return headers; } + + /** + * Returns the {@link AuditableBeanWrapper} for the given source. + * + * @param source can be {@literal null}. + * @return + */ + protected AuditableBeanWrapper getAuditableBeanWrapper(Object source) { + return auditableBeanWrapperFactory.getBeanWrapperFor(source); + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index f3417db12..2c439f852 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.convert.ConversionService; +import org.springframework.data.auditing.AuditableBeanWrapper; import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyAccessor; @@ -62,6 +63,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -301,8 +303,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem */ @RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET) public ResponseEntity> getItemResource(RootResourceInformation resourceInformation, - @BackendId Serializable id, PersistentEntityResourceAssembler assembler) - throws HttpRequestMethodNotSupportedException { + @BackendId Serializable id, PersistentEntityResourceAssembler assembler, + @RequestHeader MultiValueMap rawHeaders) throws HttpRequestMethodNotSupportedException { Object domainObj = getItemResource(resourceInformation, id); @@ -310,10 +312,32 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem return new ResponseEntity>(HttpStatus.NOT_FOUND); } - PersistentEntityResource resource = assembler.toFullResource(domainObj); - HttpHeaders headers = prepareHeaders(resource); + HttpHeaders headers = new HttpHeaders(); + headers.putAll(rawHeaders); - return new ResponseEntity>(resource, headers, HttpStatus.OK); + // Check ETag for If-Non-Match + + List ifNoneMatch = headers.getIfNoneMatch(); + ETag eTag = ifNoneMatch.isEmpty() ? ETag.NO_ETAG : ETag.from(ifNoneMatch.get(0)); + + if (eTag.matches(resourceInformation.getPersistentEntity(), domainObj)) { + return new ResponseEntity>(HttpStatus.NOT_MODIFIED); + } + + // Check last modification for If-Modfied-Since + + if (headers.getIfModifiedSince() != -1) { + + AuditableBeanWrapper wrapper = getAuditableBeanWrapper(domainObj); + long current = wrapper.getLastModifiedDate().getTimeInMillis() / 1000 * 1000; + + if (current <= headers.getIfModifiedSince()) { + return new ResponseEntity>(HttpStatus.NOT_MODIFIED); + } + } + + PersistentEntityResource resource = assembler.toFullResource(domainObj); + return new ResponseEntity>(resource, prepareHeaders(resource), HttpStatus.OK); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java index ea314b45d..0550f6c7c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java @@ -246,6 +246,10 @@ public class PersistentEntityJackson2Module extends SimpleModule { continue; } + if (persistentProperty.isVersionProperty()) { + continue; + } + result.add(writer); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java index 38917b4db..de211824b 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,6 +85,23 @@ public final class ETag { } } + /** + * Returns whether the {@link ETag} matches the given {@link PersistentEntity} and target. A more dissenting way of + * checking matches as it does not match if the ETag is {@link #NO_ETAG}. + * + * @param entity must not be {@literal null}. + * @param target can be {@literal null}. + * @return + */ + public boolean matches(PersistentEntity entity, Object target) { + + if (this == NO_ETAG || target == null) { + return false; + } + + return this.equals(from(entity, target)); + } + /** * Adds the current {@link ETag} to the given headers. * diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java index 32c164fa9..90f18c55e 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java @@ -178,8 +178,8 @@ public class MongoWebTests extends CommonWebTests { Link receiptLink = client.discoverUnique("receipts"); Receipt receipt = new Receipt(); - receipt.setAmount(new BigDecimal(50)); - receipt.setSaleItem("Springy Tacos"); + receipt.amount = new BigDecimal(50); + receipt.saleItem = "Springy Tacos"; String stringReceipt = mapper.writeValueAsString(receipt); @@ -274,4 +274,28 @@ public class MongoWebTests extends CommonWebTests { client.follow(profileLink).andExpect(jsonPath("$.metadata.Key").value("Value")); } + + /** + * @see DATAREST-506 + */ + @Test + public void supportsConditionalGetsOnItemResource() throws Exception { + + Receipt receipt = new Receipt(); + receipt.amount = new BigDecimal(50); + receipt.saleItem = "Springy Tacos"; + + Link receiptsLink = client.discoverUnique("receipts"); + + MockHttpServletResponse response = postAndGet(receiptsLink, mapper.writeValueAsString(receipt), + MediaType.APPLICATION_JSON); + + Link receiptLink = client.getDiscoverer(response).findLinkWithRel("self", response.getContentAsString()); + + mvc.perform(get(receiptLink.getHref()).header("If-Modified-Since", response.getHeader("Last-Modified"))).// + andExpect(status().isNotModified()); + + mvc.perform(get(receiptLink.getHref()).header("If-None-Match", response.getHeader("ETag"))).// + andExpect(status().isNotModified()); + } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Receipt.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Receipt.java index f617e0e40..ad5d568db 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Receipt.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Receipt.java @@ -16,59 +16,25 @@ package org.springframework.data.rest.webmvc.mongodb; +import java.math.BigDecimal; +import java.util.Date; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.Version; import org.springframework.data.mongodb.core.mapping.Document; -import java.math.BigDecimal; - - /** * @author Pablo Lozano */ @Document public class Receipt { - @Id - public String id; + public @Id String id; + public @Version Long version; + public @LastModifiedDate Date date; - private String saleItem; + public String saleItem; + public BigDecimal amount; - private BigDecimal amount; - - @Version - private Long version; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getSaleItem() { - return saleItem; - } - - public void setSaleItem(String saleItem) { - this.saleItem = saleItem; - } - - public BigDecimal getAmount() { - return amount; - } - - public void setAmount(BigDecimal amount) { - this.amount = amount; - } - - public Long getVersion() { - return version; - } - - public void setVersion(Long version) { - this.version = version; - } }