Skip to content

Commit f3c74ac

Browse files
jrrickardodrotbohm
authored andcommitted
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.
1 parent 4f8298c commit f3c74ac

File tree

4 files changed

+157
-28
lines changed

4 files changed

+157
-28
lines changed

spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public class RepositoryRestConfiguration {
4646
private String sortParamName = "sort";
4747
private MediaType defaultMediaType = MediaTypes.HAL_JSON;
4848
private boolean useHalAsDefaultJsonMediaType = true;
49-
private boolean returnBodyOnCreate = false;
50-
private boolean returnBodyOnUpdate = false;
49+
private Boolean returnBodyOnCreate = Boolean.FALSE;
50+
private Boolean returnBodyOnUpdate = Boolean.FALSE;
5151
private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
5252
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
5353
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
@@ -296,39 +296,43 @@ public RepositoryRestConfiguration useHalAsDefaultJsonMediaType(boolean useHalAs
296296
/**
297297
* Whether to return a response body after creating an entity.
298298
*
299-
* @return {@literal true} to return a body on create, {@literal false} otherwise.
299+
* @return {@link java.lang.Boolean#TRUE} to return a body on create, {@link java.lang.Boolean#FALSE} otherwise.
300+
* If {@literal null}, defer to HTTP Accept header
300301
*/
301-
public boolean isReturnBodyOnCreate() {
302+
public Boolean isReturnBodyOnCreate() {
302303
return returnBodyOnCreate;
303304
}
304305

305306
/**
306307
* Set whether to return a response body after creating an entity.
307308
*
308-
* @param returnBodyOnCreate {@literal true} to return a body on create, {@literal false} otherwise.
309+
* @param returnBodyOnCreate {@link java.lang.Boolean#TRUE} to return a body on create, {@link java.lang.Boolean#FALSE} otherwise.
310+
* If {@literal null}, defer to HTTP Accept header
309311
* @return {@literal this}
310312
*/
311-
public RepositoryRestConfiguration setReturnBodyOnCreate(boolean returnBodyOnCreate) {
313+
public RepositoryRestConfiguration setReturnBodyOnCreate(Boolean returnBodyOnCreate) {
312314
this.returnBodyOnCreate = returnBodyOnCreate;
313315
return this;
314316
}
315317

316318
/**
317319
* Whether to return a response body after updating an entity.
318320
*
319-
* @return {@literal true} to return a body on update, {@literal false} otherwise.
321+
* @return {@link java.lang.Boolean#TRUE} to return a body on update, {@link java.lang.Boolean#FALSE} otherwise.
322+
* If {@literal null}, defer to HTTP Accept header
320323
*/
321-
public boolean isReturnBodyOnUpdate() {
324+
public Boolean isReturnBodyOnUpdate() {
322325
return returnBodyOnUpdate;
323326
}
324327

325328
/**
326-
* Sets whether to return a response body after updating an entity.
327-
*
328-
* @param returnBodyOnUpdate
329-
* @return
329+
* Set whether to return a response body after updating an entity.
330+
*
331+
* @param returnBodyOnUpdate {@link java.lang.Boolean#TRUE} to return a body on update, {@link java.lang.Boolean#FALSE} otherwise.
332+
* If {@literal null}, defer to HTTP Accept header
333+
* @return {@literal this}
330334
*/
331-
public RepositoryRestConfiguration setReturnBodyOnUpdate(boolean returnBodyOnUpdate) {
335+
public RepositoryRestConfiguration setReturnBodyOnUpdate(Boolean returnBodyOnUpdate) {
332336
this.returnBodyOnUpdate = returnBodyOnUpdate;
333337
return this;
334338
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.springframework.http.MediaType;
6464
import org.springframework.http.ResponseEntity;
6565
import org.springframework.web.HttpRequestMethodNotSupportedException;
66+
import org.springframework.web.bind.annotation.RequestHeader;
6667
import org.springframework.web.bind.annotation.RequestMapping;
6768
import org.springframework.web.bind.annotation.RequestMethod;
6869
import org.springframework.web.bind.annotation.ResponseBody;
@@ -81,6 +82,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
8182
RestMediaTypes.JSON_PATCH_JSON.toString(), //
8283
MediaType.APPLICATION_JSON_VALUE);
8384

85+
private static final String ACCEPT_HEADER = "Accept";
86+
8487
private final RepositoryEntityLinks entityLinks;
8588
private final RepositoryRestConfiguration config;
8689
private final ConversionService conversionService;
@@ -227,18 +230,23 @@ public Resources<?> getCollectionResourceCompact(RootResourceInformation repoReq
227230
*
228231
* @param resourceInformation
229232
* @param payload
233+
* @param assembler
234+
* @param acceptHeader
230235
* @return
231236
* @throws HttpRequestMethodNotSupportedException
232237
*/
233238
@ResponseBody
234239
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST)
235240
public ResponseEntity<ResourceSupport> postCollectionResource(RootResourceInformation resourceInformation,
236-
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler)
241+
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler,
242+
@RequestHeader(value= ACCEPT_HEADER, required = false) String acceptHeader)
237243
throws HttpRequestMethodNotSupportedException {
238244

239245
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
240246

241-
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler);
247+
boolean acceptHeaderPresent = acceptHeader != null;
248+
249+
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler, acceptHeaderPresent);
242250
}
243251

244252
/**
@@ -312,17 +320,21 @@ public ResponseEntity<Resource<?>> getItemResource(RootResourceInformation resou
312320
/**
313321
* <code>PUT /{repository}/{id}</code> - Updates an existing entity or creates one at exactly that place.
314322
*
315-
* @param eTagMatch
323+
316324
* @param resourceInformation
317325
* @param payload
318326
* @param id
327+
* @param assembler
328+
* @param eTag
329+
* @param acceptHeader
319330
* @return
320331
* @throws HttpRequestMethodNotSupportedException
321332
*/
322333
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT)
323334
public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInformation resourceInformation,
324335
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler,
325-
ETag eTag) throws HttpRequestMethodNotSupportedException {
336+
ETag eTag, @RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader)
337+
throws HttpRequestMethodNotSupportedException {
326338

327339
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
328340

@@ -338,8 +350,10 @@ public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInf
338350

339351
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
340352

341-
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler) : saveAndReturn(objectToSave,
342-
invoker, PUT, assembler);
353+
boolean acceptHeaderPresent = acceptHeader != null;
354+
355+
return domainObject == null ? createAndReturn(objectToSave, invoker, assembler, acceptHeaderPresent)
356+
: saveAndReturn(objectToSave, invoker, PUT, assembler, acceptHeaderPresent);
343357
}
344358

345359
/**
@@ -349,7 +363,8 @@ public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInf
349363
* @param payload
350364
* @param id
351365
* @param assembler
352-
* @param eTag
366+
* @param eTag,
367+
* @param acceptHeader
353368
* @return
354369
* @throws HttpRequestMethodNotSupportedException
355370
* @throws ResourceNotFoundException
@@ -358,7 +373,8 @@ public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInf
358373
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PATCH)
359374
public ResponseEntity<ResourceSupport> patchItemResource(RootResourceInformation resourceInformation,
360375
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler,
361-
ETag eTag) throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
376+
ETag eTag,@RequestHeader(value=ACCEPT_HEADER, required = false) String acceptHeader )
377+
throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
362378

363379
resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM);
364380

@@ -370,7 +386,9 @@ public ResponseEntity<ResourceSupport> patchItemResource(RootResourceInformation
370386

371387
eTag.verify(resourceInformation.getPersistentEntity(), domainObject);
372388

373-
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler);
389+
boolean acceptHeaderPresent = acceptHeader != null;
390+
391+
return saveAndReturn(payload.getContent(), resourceInformation.getInvoker(), PATCH, assembler, acceptHeaderPresent);
374392
}
375393

376394
/**
@@ -415,7 +433,7 @@ public ResponseEntity<?> deleteItemResource(RootResourceInformation resourceInfo
415433
* @return
416434
*/
417435
private ResponseEntity<ResourceSupport> saveAndReturn(Object domainObject, RepositoryInvoker invoker,
418-
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler) {
436+
HttpMethod httpMethod, PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
419437

420438
publisher.publishEvent(new BeforeSaveEvent(domainObject));
421439
Object obj = invoker.invokeSave(domainObject);
@@ -428,7 +446,10 @@ private ResponseEntity<ResourceSupport> saveAndReturn(Object domainObject, Repos
428446
addLocationHeader(headers, assembler, obj);
429447
}
430448

431-
if (config.isReturnBodyOnUpdate()) {
449+
boolean returnBodyOnUpdate = (config.isReturnBodyOnUpdate() == null && acceptHeaderPresent)
450+
|| Boolean.TRUE.equals(config.isReturnBodyOnUpdate());
451+
452+
if (returnBodyOnUpdate) {
432453
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, resource);
433454
} else {
434455
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT, headers);
@@ -443,13 +464,17 @@ private ResponseEntity<ResourceSupport> saveAndReturn(Object domainObject, Repos
443464
* @return
444465
*/
445466
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, RepositoryInvoker invoker,
446-
PersistentEntityResourceAssembler assembler) {
467+
PersistentEntityResourceAssembler assembler, boolean acceptHeaderPresent) {
447468

448469
publisher.publishEvent(new BeforeCreateEvent(domainObject));
449470
Object savedObject = invoker.invokeSave(domainObject);
450471
publisher.publishEvent(new AfterCreateEvent(savedObject));
451472

452-
PersistentEntityResource resource = config.isReturnBodyOnCreate() ? assembler.toFullResource(savedObject) : null;
473+
474+
boolean returnBodyOnCreate = (config.isReturnBodyOnCreate() == null && acceptHeaderPresent)
475+
|| Boolean.TRUE.equals(config.isReturnBodyOnCreate());
476+
477+
PersistentEntityResource resource = returnBodyOnCreate ? assembler.toFullResource(savedObject) : null;
453478

454479
HttpHeaders headers = prepareHeaders(resource);
455480
addLocationHeader(headers, assembler, savedObject);

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.util.List;
2424

25+
import org.junit.After;
2526
import org.junit.Test;
2627
import org.springframework.beans.factory.annotation.Autowired;
2728
import org.springframework.data.mapping.context.PersistentEntities;
@@ -33,6 +34,7 @@
3334
import org.springframework.data.rest.webmvc.jpa.Order;
3435
import org.springframework.data.rest.webmvc.jpa.Person;
3536
import org.springframework.data.rest.webmvc.support.ETag;
37+
import org.springframework.hateoas.ResourceSupport;
3638
import org.springframework.http.HttpEntity;
3739
import org.springframework.http.HttpStatus;
3840
import org.springframework.http.MediaType;
@@ -46,6 +48,7 @@
4648
*
4749
* @author Oliver Gierke
4850
*/
51+
@SuppressWarnings("ALL")
4952
@ContextConfiguration(classes = JpaRepositoryConfig.class)
5053
@Transactional
5154
public class RepositoryEntityControllerIntegrationTests extends AbstractControllerIntegrationTests {
@@ -76,7 +79,7 @@ public void rejectsEntityCreationIfSaveIsNotExported() throws Exception {
7679

7780
RootResourceInformation request = getResourceInformation(Address.class);
7881

79-
controller.postCollectionResource(request, null, null);
82+
controller.postCollectionResource(request, null, null, MediaType.APPLICATION_JSON_VALUE);
8083
}
8184

8285
/**
@@ -91,7 +94,7 @@ public void setsExpandedSelfUriInLocationHeader() throws Exception {
9194
entities.getPersistentEntity(Order.class)).build();
9295

9396
ResponseEntity<?> entity = controller.putItemResource(information, persistentEntityResource, 1L, assembler,
94-
ETag.NO_ETAG);
97+
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
9598

9699
assertThat(entity.getHeaders().getLocation().toString(), not(endsWith("{?projection}")));
97100
}
@@ -181,4 +184,99 @@ public void optionsForItermResourceSetsAllowPatchHeader() {
181184
RestMediaTypes.MERGE_PATCH_JSON.toString(), //
182185
MediaType.APPLICATION_JSON_VALUE));
183186
}
187+
188+
/**
189+
* @see DATAREST-34
190+
*/
191+
@Test
192+
public void verifyAcceptHeaderCanControlBodyReturnOnPutItemResource() throws HttpRequestMethodNotSupportedException {
193+
RootResourceInformation request = getResourceInformation(Order.class);
194+
195+
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
196+
entities.getPersistentEntity(Order.class)).build();
197+
198+
configuration.setReturnBodyOnCreate(Boolean.FALSE);
199+
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
200+
201+
ResponseEntity<?> response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
202+
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
203+
204+
assert(!response.hasBody());
205+
206+
207+
configuration.setReturnBodyOnCreate(Boolean.TRUE);
208+
configuration.setReturnBodyOnUpdate(Boolean.TRUE);
209+
210+
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
211+
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
212+
213+
configuration.setReturnBodyOnCreate(Boolean.FALSE);
214+
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
215+
216+
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
217+
ETag.NO_ETAG, null);
218+
219+
assert(!response.hasBody());
220+
221+
configuration.setReturnBodyOnCreate(null);
222+
configuration.setReturnBodyOnUpdate(null);
223+
224+
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
225+
ETag.NO_ETAG, null);
226+
227+
assert(!response.hasBody());
228+
229+
configuration.setReturnBodyOnCreate(null);
230+
configuration.setReturnBodyOnUpdate(null);
231+
232+
response = controller.putItemResource(request, persistentEntityResource, 1L, assembler,
233+
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
234+
235+
assert(response.hasBody());
236+
}
237+
238+
/**
239+
* @see DATAREST-34
240+
*/
241+
@Test
242+
public void verifyAcceptHeaderCanControlBodyReturnPostCollectionResource() throws HttpRequestMethodNotSupportedException {
243+
RootResourceInformation request = getResourceInformation(Order.class);
244+
245+
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
246+
entities.getPersistentEntity(Order.class)).build();
247+
248+
configuration.setReturnBodyOnCreate(null);
249+
250+
ResponseEntity<ResourceSupport> response =
251+
controller.postCollectionResource(request, persistentEntityResource, assembler, MediaType.APPLICATION_JSON_VALUE);
252+
253+
254+
assert(response.hasBody());
255+
256+
257+
response =
258+
controller.postCollectionResource(request, persistentEntityResource, assembler, null);
259+
260+
261+
assert(!response.hasBody());
262+
263+
264+
configuration.setReturnBodyOnCreate(Boolean.FALSE);
265+
response =
266+
controller.postCollectionResource(request, persistentEntityResource, assembler, MediaType.APPLICATION_JSON_VALUE);
267+
268+
assert(!response.hasBody());
269+
270+
configuration.setReturnBodyOnCreate(Boolean.TRUE);
271+
response =
272+
controller.postCollectionResource(request, persistentEntityResource, assembler, null);
273+
274+
assert(response.hasBody());
275+
}
276+
277+
@After
278+
public void cleanUp() {
279+
configuration.setReturnBodyOnCreate(Boolean.FALSE);
280+
configuration.setReturnBodyOnUpdate(Boolean.FALSE);
281+
}
184282
}

src/main/asciidoc/repository-resources.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ For the resources exposed, we use a set of default status codes:
2424
* `201 Created` - for `POST` and `PUT` requests that create new resources.
2525
* `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`.
2626

27+
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.
28+
2729
[[repository-resources.resource-discoverability]]
2830
=== Resource discoverability
2931

0 commit comments

Comments
 (0)