DATAREST-1121 - Skip last modified detection for query methods that project.

For an execution of a projecting query method we now skip the last modified detection as it actually doesn't make sense if applied to non-aggregates.
This commit is contained in:
Oliver Gierke
2017-08-24 21:50:31 +02:00
parent 5e5c2fd89b
commit 530e7c773e
6 changed files with 54 additions and 3 deletions

View File

@@ -51,4 +51,8 @@ public interface PersonRepository extends PagingAndSortingRepository<Person, Lon
@Query("select p from Person p where p.created > :date")
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
Pageable pageable);
// DATAREST-1121
@Query("select p.created from Person p where p.lastName = :lastname")
Date findCreatedDateByLastName(@Param("lastname") String lastName);
}

View File

@@ -41,6 +41,7 @@ import org.springframework.hateoas.Resources;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
@@ -74,7 +75,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
ResourceSupport resource = controller.listSearches(request);
ResourceTester tester = ResourceTester.of(resource);
tester.assertNumberOfLinks(6); // Self link included
tester.assertNumberOfLinks(7); // Self link included
tester.assertHasLinkEndingWith("findFirstPersonByFirstName", "findFirstPersonByFirstName{?firstname,projection}");
tester.assertHasLinkEndingWith("firstname", "firstname{?firstname,page,size,sort,projection}");
tester.assertHasLinkEndingWith("lastname", "lastname{?lastname,sort,projection}");
@@ -82,6 +83,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
"findByCreatedUsingISO8601Date{?date,page,size,sort,projection}");
tester.assertHasLinkEndingWith("findByCreatedGreaterThan",
"findByCreatedGreaterThan{?date,page,size,sort,projection}");
tester.assertHasLinkEndingWith("findCreatedDateByLastName", "findCreatedDateByLastName{?lastname}");
}
@Test(expected = ResourceNotFoundException.class)
@@ -177,4 +179,17 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
assertThat(searches.getDomainType()).isAssignableFrom(Person.class);
}
@Test // DATAREST-1121
public void returnsSimpleResponseEntityForQueryMethod() {
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>();
parameters.add("lastname", "Thornton");
ResponseEntity<?> entity = controller.executeSearch(getResourceInformation(Person.class), parameters,
"findCreatedDateByLastName", PAGEABLE, Sort.unsorted(), assembler, new HttpHeaders());
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders()).isEmpty();
}
}

View File

@@ -73,6 +73,9 @@ public class HttpHeadersPreparer {
*/
public HttpHeaders prepareHeaders(PersistentEntity<?, ?> entity, Object value) {
Assert.notNull(entity, "PersistentEntity must not be null!");
Assert.notNull(value, "Entity value must not be null!");
// Add ETag
HttpHeaders headers = ETag.from(entity, value).addTo(new HttpHeaders());

View File

@@ -213,6 +213,11 @@ class RepositorySearchController extends AbstractRepositoryRestController {
PersistentEntity<?, ?> entity = information.getPersistentEntity();
// Returned value is not of the aggregates type - probably some projection
if (!entity.getType().isInstance(it)) {
return ResponseEntity.ok(it);
}
return resourceStatus.getStatusAndHeaders(headers, it, entity).toResponseEntity(//
() -> assembler.toFullResource(it));

View File

@@ -29,6 +29,7 @@ import org.springframework.hateoas.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
/**
* Simple abstraction to capture the status of a resource to determine whether it has been modified or not and produce
@@ -41,6 +42,8 @@ import org.springframework.http.ResponseEntity;
@RequiredArgsConstructor(staticName = "of")
class ResourceStatus {
private static final String INVALID_DOMAIN_OBJECT = "Domain object %s is not an instance of the given PersistentEntity of type %s!";
private final @NonNull HttpHeadersPreparer preparer;
/**
@@ -49,12 +52,18 @@ class ResourceStatus {
*
* @param requestHeaders must not be {@literal null}.
* @param domainObject must not be {@literal null}.
* @param information
* @param entity must not be {@literal null}.
* @return
*/
public StatusAndHeaders getStatusAndHeaders(HttpHeaders requestHeaders, Object domainObject,
PersistentEntity<?, ?> entity) {
Assert.notNull(requestHeaders, "Request headers must not be null!");
Assert.notNull(domainObject, "Domain object must not be null!");
Assert.notNull(entity, "PersistentEntity must not be null!");
Assert.isTrue(entity.getType().isInstance(domainObject),
String.format(INVALID_DOMAIN_OBJECT, domainObject, entity.getType()));
// Check ETag for If-Non-Match
List<String> ifNoneMatch = requestHeaders.getIfNoneMatch();
@@ -64,7 +73,8 @@ class ResourceStatus {
// Check last modification for If-Modified-Since
return eTag.matches(entity, domainObject) || preparer.isObjectStillValid(domainObject, requestHeaders)
? StatusAndHeaders.notModified(responseHeaders) : StatusAndHeaders.modified(responseHeaders);
? StatusAndHeaders.notModified(responseHeaders)
: StatusAndHeaders.modified(responseHeaders);
}
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)

View File

@@ -21,10 +21,13 @@ import static org.mockito.Mockito.*;
import lombok.Value;
import java.util.Date;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -49,6 +52,8 @@ public class ResourceStatusUnitTests {
@Mock HttpHeadersPreparer preparer;
@Mock Supplier<PersistentEntityResource> supplier;
public @Rule ExpectedException exception = ExpectedException.none();
@Before
public void setUp() {
@@ -87,6 +92,15 @@ public class ResourceStatusUnitTests {
assertNotModified(status.getStatusAndHeaders(new HttpHeaders(), new Sample(0), entity));
}
@Test // DATAREST-1121
public void rejectsInvalidPersistentEntityDomainObjectCombination() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(entity.getType().getName());
assertModified(status.getStatusAndHeaders(new HttpHeaders(), new Date(), entity));
}
private void assertModified(StatusAndHeaders statusAndHeaders) {
assertThat(statusAndHeaders.isModified()).isTrue();