DATAREST-34 - PUT and POST request now consider Accept header by default.
By default, whether to return response bodies for PUT and POST is determined by the presence of an Accept header, unless explicitly activated or deactivated in RepositoryRestConfiguration. Original pull request: #167.
This commit is contained in:
committed by
Oliver Gierke
parent
4f8298c711
commit
f3c74ac9db
@@ -46,8 +46,8 @@ public class RepositoryRestConfiguration {
|
||||
private String sortParamName = "sort";
|
||||
private MediaType defaultMediaType = MediaTypes.HAL_JSON;
|
||||
private boolean useHalAsDefaultJsonMediaType = true;
|
||||
private boolean returnBodyOnCreate = false;
|
||||
private boolean returnBodyOnUpdate = false;
|
||||
private Boolean returnBodyOnCreate = Boolean.FALSE;
|
||||
private Boolean returnBodyOnUpdate = Boolean.FALSE;
|
||||
private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
|
||||
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
|
||||
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
|
||||
@@ -296,19 +296,21 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Whether to return a response body after creating an entity.
|
||||
*
|
||||
* @return {@literal true} to return a body on create, {@literal false} otherwise.
|
||||
* @return {@link java.lang.Boolean#TRUE} to return a body on create, {@link java.lang.Boolean#FALSE} otherwise.
|
||||
* If {@literal null}, defer to HTTP Accept header
|
||||
*/
|
||||
public boolean isReturnBodyOnCreate() {
|
||||
public Boolean isReturnBodyOnCreate() {
|
||||
return returnBodyOnCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to return a response body after creating an entity.
|
||||
*
|
||||
* @param returnBodyOnCreate {@literal true} to return a body on create, {@literal false} otherwise.
|
||||
* @param returnBodyOnCreate {@link java.lang.Boolean#TRUE} to return a body on create, {@link java.lang.Boolean#FALSE} otherwise.
|
||||
* If {@literal null}, defer to HTTP Accept header
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setReturnBodyOnCreate(boolean returnBodyOnCreate) {
|
||||
public RepositoryRestConfiguration setReturnBodyOnCreate(Boolean returnBodyOnCreate) {
|
||||
this.returnBodyOnCreate = returnBodyOnCreate;
|
||||
return this;
|
||||
}
|
||||
@@ -316,19 +318,21 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Whether to return a response body after updating an entity.
|
||||
*
|
||||
* @return {@literal true} to return a body on update, {@literal false} otherwise.
|
||||
* @return {@link java.lang.Boolean#TRUE} to return a body on update, {@link java.lang.Boolean#FALSE} otherwise.
|
||||
* If {@literal null}, defer to HTTP Accept header
|
||||
*/
|
||||
public boolean isReturnBodyOnUpdate() {
|
||||
public Boolean isReturnBodyOnUpdate() {
|
||||
return returnBodyOnUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to return a response body after updating an entity.
|
||||
*
|
||||
* @param returnBodyOnUpdate
|
||||
* @return
|
||||
* Set whether to return a response body after updating an entity.
|
||||
*
|
||||
* @param returnBodyOnUpdate {@link java.lang.Boolean#TRUE} to return a body on update, {@link java.lang.Boolean#FALSE} otherwise.
|
||||
* If {@literal null}, defer to HTTP Accept header
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setReturnBodyOnUpdate(boolean returnBodyOnUpdate) {
|
||||
public RepositoryRestConfiguration setReturnBodyOnUpdate(Boolean returnBodyOnUpdate) {
|
||||
this.returnBodyOnUpdate = returnBodyOnUpdate;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -81,6 +82,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
RestMediaTypes.JSON_PATCH_JSON.toString(), //
|
||||
MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
private static final String ACCEPT_HEADER = "Accept";
|
||||
|
||||
private final RepositoryEntityLinks entityLinks;
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final ConversionService conversionService;
|
||||
@@ -227,18 +230,23 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
*
|
||||
* @param resourceInformation
|
||||
* @param payload
|
||||
* @param assembler
|
||||
* @param acceptHeader
|
||||
* @return
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
*/
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST)
|
||||
public ResponseEntity<ResourceSupport> postCollectionResource(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler)
|
||||
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler,
|
||||
@RequestHeader(value= ACCEPT_HEADER, required = false) String acceptHeader)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
|
||||
|
||||
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler);
|
||||
boolean acceptHeaderPresent = acceptHeader != null;
|
||||
|
||||
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler, acceptHeaderPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,17 +320,21 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
/**
|
||||
* <code>PUT /{repository}/{id}</code> - Updates an existing entity or creates one at exactly that place.
|
||||
*
|
||||
* @param eTagMatch
|
||||
|
||||
* @param resourceInformation
|
||||
* @param payload
|
||||
* @param id
|
||||
* @param assembler
|
||||
* @param eTag
|
||||
* @param acceptHeader
|
||||
* @return
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT)
|
||||
public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler,
|
||||
ETag eTag) throws HttpRequestMethodNotSupportedException {
|
||||
ETag eTag, @RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
|
||||
|
||||
@@ -338,8 +350,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
|
||||
|
||||
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler) : saveAndReturn(objectToSave,
|
||||
invoker, PUT, assembler);
|
||||
boolean acceptHeaderPresent = acceptHeader != null;
|
||||
|
||||
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler, acceptHeaderPresent)
|
||||
: saveAndReturn(objectToSave, invoker, PUT, assembler, acceptHeaderPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +363,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @param payload
|
||||
* @param id
|
||||
* @param assembler
|
||||
* @param eTag
|
||||
* @param eTag,
|
||||
* @param acceptHeader
|
||||
* @return
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
* @throws ResourceNotFoundException
|
||||
@@ -358,7 +373,8 @@ 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) throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
|
||||
ETag eTag,@RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader )
|
||||
throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM);
|
||||
|
||||
@@ -370,7 +386,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
|
||||
|
||||
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler);
|
||||
boolean acceptHeaderPresent = acceptHeader != null;
|
||||
|
||||
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler, acceptHeaderPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,7 +433,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @return
|
||||
*/
|
||||
private ResponseEntity<ResourceSupport> saveAndReturn(Object domainObject, RepositoryInvoker invoker,
|
||||
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler) {
|
||||
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
|
||||
|
||||
publisher.publishEvent(new BeforeSaveEvent(domainObject));
|
||||
Object obj = invoker.invokeSave(domainObject);
|
||||
@@ -428,7 +446,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
addLocationHeader(headers, assembler, obj);
|
||||
}
|
||||
|
||||
if (config.isReturnBodyOnUpdate()) {
|
||||
boolean returnBodyOnUpdate = (config.isReturnBodyOnUpdate() == null && acceptHeaderPresent)
|
||||
|| Boolean.TRUE.equals(config.isReturnBodyOnUpdate());
|
||||
|
||||
if (returnBodyOnUpdate) {
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, resource);
|
||||
} else {
|
||||
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT, headers);
|
||||
@@ -443,13 +464,17 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @return
|
||||
*/
|
||||
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, RepositoryInvoker invoker,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
|
||||
|
||||
publisher.publishEvent(new BeforeCreateEvent(domainObject));
|
||||
Object savedObject = invoker.invokeSave(domainObject);
|
||||
publisher.publishEvent(new AfterCreateEvent(savedObject));
|
||||
|
||||
PersistentEntityResource resource = config.isReturnBodyOnCreate() ? assembler.toFullResource(savedObject) : null;
|
||||
|
||||
boolean returnBodyOnCreate = (config.isReturnBodyOnCreate() == null && acceptHeaderPresent)
|
||||
|| Boolean.TRUE.equals(config.isReturnBodyOnCreate());
|
||||
|
||||
PersistentEntityResource resource = returnBodyOnCreate ? assembler.toFullResource(savedObject) : null;
|
||||
|
||||
HttpHeaders headers = prepareHeaders(resource);
|
||||
addLocationHeader(headers, assembler, savedObject);
|
||||
|
||||
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -33,6 +34,7 @@ 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;
|
||||
@@ -46,6 +48,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
@Transactional
|
||||
public class RepositoryEntityControllerIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
@@ -76,7 +79,7 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
|
||||
RootResourceInformation request = getResourceInformation(Address.class);
|
||||
|
||||
controller.postCollectionResource(request, null, null);
|
||||
controller.postCollectionResource(request, null, null, MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +94,7 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
entities.getPersistentEntity(Order.class)).build();
|
||||
|
||||
ResponseEntity<?> entity = controller.putItemResource(information, persistentEntityResource, 1L, assembler,
|
||||
ETag.NO_ETAG);
|
||||
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
assertThat(entity.getHeaders().getLocation().toString(), not(endsWith("{?projection}")));
|
||||
}
|
||||
@@ -181,4 +184,99 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
RestMediaTypes.MERGE_PATCH_JSON.toString(), //
|
||||
MediaType.APPLICATION_JSON_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-34
|
||||
*/
|
||||
@Test
|
||||
public void verifyAcceptHeaderCanControlBodyReturnOnPutItemResource() throws HttpRequestMethodNotSupportedException {
|
||||
RootResourceInformation request = getResourceInformation(Order.class);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-34
|
||||
*/
|
||||
@Test
|
||||
public void verifyAcceptHeaderCanControlBodyReturnPostCollectionResource() 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());
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
configuration.setReturnBodyOnCreate(Boolean.FALSE);
|
||||
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ For the resources exposed, we use a set of default status codes:
|
||||
* `201 Created` - for `POST` and `PUT` requests that create new resources.
|
||||
* `204 No Content` - for `PUT`, `PATCH`, and `DELETE` requests if the configuration is set to not return response bodies for resource updates (`RepositoryRestConfiguration.returnBodyOnUpdate`). If the configuration value is set to include responses for `PUT`, `200 OK` will be returned for updates, `201 Created` will be returned for resource created through `PUT`.
|
||||
|
||||
If the configuration values (`RepositoryRestConfiguration.returnBodyOnUpdate` and `RepositoryRestConfiguration.returnBodyCreate)` are explicitly set to null, the presence of the HTTP Accept header will be used to determine the response code.
|
||||
|
||||
[[repository-resources.resource-discoverability]]
|
||||
=== Resource discoverability
|
||||
|
||||
|
||||
Reference in New Issue
Block a user