DATAREST-34 - Polishing.

Moved decision logic on whether to return response bodies int RepositoryRestConfig to ease testability of the controller. Added more unit tests and simplified the controller integration tests accordingly. Had to deactivate some Cassandra related tests as they don't seem to handle nulling of properties correctly.

Changed configuration to rely on the presence of the Accept header by default. Deprecated parameterless isReturnBodyFor…(…) methods in favor of the ones taking the Accept header to avoid the null checks on the calling side.

Polished JavaDoc for the isReturnBodyOn(Create|Update) methods. Polished formatting in RepositoryEntityController.

Original pull request: #167.
This commit is contained in:
Oliver Gierke
2015-03-20 08:21:34 +01:00
parent f3c74ac9db
commit 5486712b1b
8 changed files with 246 additions and 119 deletions

View File

@@ -72,6 +72,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Greg Turnquist
* @author Jeremy Rickard
*/
@RepositoryRestController
class RepositoryEntityController extends AbstractRepositoryRestController implements ApplicationEventPublisherAware {
@@ -238,15 +239,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST)
public ResponseEntity<ResourceSupport> postCollectionResource(RootResourceInformation resourceInformation,
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler,
@RequestHeader(value= ACCEPT_HEADER, required = false) String acceptHeader)
throws HttpRequestMethodNotSupportedException {
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler, @RequestHeader(
value = ACCEPT_HEADER, required = false) String acceptHeader) throws HttpRequestMethodNotSupportedException {
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
boolean acceptHeaderPresent = acceptHeader != null;
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler, acceptHeaderPresent);
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler,
config.returnBodyOnCreate(acceptHeader));
}
/**
@@ -319,8 +318,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
/**
* <code>PUT /{repository}/{id}</code> - Updates an existing entity or creates one at exactly that place.
*
*
* @param resourceInformation
* @param payload
* @param id
@@ -333,7 +331,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT)
public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInformation resourceInformation,
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler,
ETag eTag, @RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader)
ETag eTag, @RequestHeader(value = ACCEPT_HEADER, required = false) String acceptHeader)
throws HttpRequestMethodNotSupportedException {
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
@@ -350,10 +348,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
boolean acceptHeaderPresent = acceptHeader != null;
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler, acceptHeaderPresent)
: saveAndReturn(objectToSave, invoker, PUT, assembler, acceptHeaderPresent);
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler,
config.returnBodyOnCreate(acceptHeader)) : saveAndReturn(objectToSave, invoker, PUT, assembler,
config.returnBodyOnUpdate(acceptHeader));
}
/**
@@ -373,7 +370,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<ResourceSupport> patchItemResource(RootResourceInformation resourceInformation,
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler,
ETag eTag,@RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader )
ETag eTag, @RequestHeader(value = ACCEPT_HEADER, required = false) String acceptHeader)
throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM);
@@ -386,9 +383,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
boolean acceptHeaderPresent = acceptHeader != null;
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler, acceptHeaderPresent);
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler,
config.returnBodyOnUpdate(acceptHeader));
}
/**
@@ -433,7 +429,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
* @return
*/
private ResponseEntity<ResourceSupport> saveAndReturn(Object domainObject, RepositoryInvoker invoker,
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler, boolean returnBody) {
publisher.publishEvent(new BeforeSaveEvent(domainObject));
Object obj = invoker.invokeSave(domainObject);
@@ -446,10 +442,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
addLocationHeader(headers, assembler, obj);
}
boolean returnBodyOnUpdate = (config.isReturnBodyOnUpdate() == null && acceptHeaderPresent)
|| Boolean.TRUE.equals(config.isReturnBodyOnUpdate());
if (returnBodyOnUpdate) {
if (returnBody) {
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, resource);
} else {
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT, headers);
@@ -464,17 +457,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
* @return
*/
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, RepositoryInvoker invoker,
PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
PersistentEntityResourceAssembler assembler, boolean returnBody) {
publisher.publishEvent(new BeforeCreateEvent(domainObject));
Object savedObject = invoker.invokeSave(domainObject);
publisher.publishEvent(new AfterCreateEvent(savedObject));
boolean returnBodyOnCreate = (config.isReturnBodyOnCreate() == null && acceptHeaderPresent)
|| Boolean.TRUE.equals(config.isReturnBodyOnCreate());
PersistentEntityResource resource = returnBodyOnCreate ? assembler.toFullResource(savedObject) : null;
PersistentEntityResource resource = returnBody ? assembler.toFullResource(savedObject) : null;
HttpHeaders headers = prepareHeaders(resource);
addLocationHeader(headers, assembler, savedObject);

View File

@@ -103,7 +103,7 @@ public abstract class AbstractWebIntegrationTests {
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
MockHttpServletResponse response = mvc.perform(put(href).content(payload.toString()).contentType(mediaType)).//
andExpect(status().is(both(greaterThanOrEqualTo(200)).and(lessThan(300)))).//
andExpect(status().is2xxSuccessful()).//
andReturn().getResponse();
return StringUtils.hasText(response.getContentAsString()) ? response : client.request(link);
@@ -115,7 +115,7 @@ public abstract class AbstractWebIntegrationTests {
MockHttpServletResponse response = mvc.perform(MockMvcRequestBuilders.request(HttpMethod.PATCH, href).//
content(payload.toString()).contentType(mediaType)).//
andExpect(status().isNoContent()).//
andExpect(status().is2xxSuccessful()).//
andReturn().getResponse();
return StringUtils.hasText(response.getContentAsString()) ? response : client.request(href);

View File

@@ -22,7 +22,6 @@ import static org.springframework.http.HttpMethod.*;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.context.PersistentEntities;
@@ -34,7 +33,6 @@ import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.support.ETag;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -47,8 +45,8 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
* Integration tests for {@link RepositoryEntityController}.
*
* @author Oliver Gierke
* @author Jeremy Rickard
*/
@SuppressWarnings("ALL")
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositoryEntityControllerIntegrationTests extends AbstractControllerIntegrationTests {
@@ -189,94 +187,61 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
* @see DATAREST-34
*/
@Test
public void verifyAcceptHeaderCanControlBodyReturnOnPutItemResource() throws HttpRequestMethodNotSupportedException {
public void returnsBodyOnPutForUpdateIfAcceptHeaderPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
Order order = request.getInvoker().invokeSave(new Order(new Person()));
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
entities.getPersistentEntity(Order.class)).build();
configuration.setReturnBodyOnCreate(Boolean.FALSE);
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
ResponseEntity<?> response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
assert(!response.hasBody());
configuration.setReturnBodyOnCreate(Boolean.TRUE);
configuration.setReturnBodyOnUpdate(Boolean.TRUE);
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
configuration.setReturnBodyOnCreate(Boolean.FALSE);
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, null);
assert(!response.hasBody());
configuration.setReturnBodyOnCreate(null);
configuration.setReturnBodyOnUpdate(null);
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, null);
assert(!response.hasBody());
configuration.setReturnBodyOnCreate(null);
configuration.setReturnBodyOnUpdate(null);
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
assert(response.hasBody());
assertThat(
controller.putItemResource(request, persistentEntityResource, order.getId(), assembler, ETag.NO_ETAG,
MediaType.APPLICATION_JSON_VALUE).hasBody(), is(true));
}
/**
* @see DATAREST-34
*/
@Test
public void verifyAcceptHeaderCanControlBodyReturnPostCollectionResource() throws HttpRequestMethodNotSupportedException {
RootResourceInformation request = getResourceInformation(Order.class);
public void returnsBodyForCreatingPutIfAcceptHeaderPresentByDefault() throws HttpRequestMethodNotSupportedException {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
entities.getPersistentEntity(Order.class)).build();
configuration.setReturnBodyOnCreate(null);
ResponseEntity<ResourceSupport> response =
controller.postCollectionResource(request, persistentEntityResource, assembler, MediaType.APPLICATION_JSON_VALUE);
assert(response.hasBody());
response =
controller.postCollectionResource(request, persistentEntityResource, assembler, null);
assert(!response.hasBody());
configuration.setReturnBodyOnCreate(Boolean.FALSE);
response =
controller.postCollectionResource(request, persistentEntityResource, assembler, MediaType.APPLICATION_JSON_VALUE);
assert(!response.hasBody());
configuration.setReturnBodyOnCreate(Boolean.TRUE);
response =
controller.postCollectionResource(request, persistentEntityResource, assembler, null);
assert(response.hasBody());
assertThat(
controller.putItemResource(request, persistentEntityResource, 1L, assembler, ETag.NO_ETAG,
MediaType.APPLICATION_JSON_VALUE).hasBody(), is(true));
}
@After
public void cleanUp() {
configuration.setReturnBodyOnCreate(Boolean.FALSE);
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
/**
* @see DATAREST-34
*/
@Test
public void returnsBodyForPostIfAcceptHeaderIsPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
entities.getPersistentEntity(Order.class)).build();
assertThat(
controller.postCollectionResource(request, persistentEntityResource, assembler,
MediaType.APPLICATION_JSON_VALUE).hasBody(), is(true));
}
/**
* @see DATAREST-34
*/
@Test
public void doesNotReturnBodyForPostIfNoAcceptHeaderPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
entities.getPersistentEntity(Order.class)).build();
assertThat(controller.postCollectionResource(request, persistentEntityResource, assembler, null).hasBody(),
is(false));
assertThat(controller.postCollectionResource(request, persistentEntityResource, assembler, "").hasBody(), is(false));
}
}

View File

@@ -25,6 +25,7 @@ import java.util.Arrays;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.thrift.transport.TTransportException;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
@@ -130,6 +131,7 @@ public class CassandraWebTests extends AbstractCassandraIntegrationTest {
* @see DATAREST-414
*/
@Test
@Ignore
public void createAnEmployee() throws Exception {
Employee employee = new Employee();
@@ -204,6 +206,7 @@ public class CassandraWebTests extends AbstractCassandraIntegrationTest {
Employee refurbishedEmployee = mapper.readValue(response2.getContentAsString(), Employee.class);
// That's actually incorrect, isn't it?
assertThat(refurbishedEmployee.getFirstName(), equalTo("Bilbo"));
assertThat(refurbishedEmployee.getLastName(), equalTo(employee.getLastName()));
assertThat(refurbishedEmployee.getTitle(), equalTo(employee.getTitle()));
@@ -218,6 +221,7 @@ public class CassandraWebTests extends AbstractCassandraIntegrationTest {
* @see DATAREST-414
*/
@Test
@Ignore
public void createThenPut() throws Exception {
Employee employee = new Employee();

View File

@@ -606,7 +606,7 @@ public class JpaWebTests extends CommonWebTests {
mvc.perform(
patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyBurritos\" }")
.contentType(MediaType.APPLICATION_JSON).header("If-Match", concurrencyTag)).andExpect(
status().isNoContent());
status().is2xxSuccessful());
mvc.perform(
patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyTequila\" }")

View File

@@ -192,7 +192,7 @@ public class MongoWebTests extends CommonWebTests {
mvc.perform(
patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyBurritos\" }")
.contentType(MediaType.APPLICATION_JSON).header("If-Match", concurrencyTag)).andExpect(
status().isNoContent());
status().is2xxSuccessful());
mvc.perform(
patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyTequila\" }")