From 17305f406799228b1e67088e10b59694392d30a2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 18 May 2015 18:01:08 +0200 Subject: [PATCH] DATAREST-479 - Empty collections now render a dedicated _embedded property. Instead of rendering an empty _embedded document for empty collections and pages we now explicitly trigger the creations of an EmbeddedWrapper for that empty, collection to preserve the collection's element type. Tweaked ResourceProcessorHandlerMethodReturnValueHandler to invoke ResourceProcessor instances for those empty collections, too. PRHMRVH now checks the assignability of the raw resource type before analyzing the value type for a match. RepositorySearchController now adds additional self links for the list of searches and a search execution. --- .../core/mapping/MethodResourceMapping.java | 2 +- .../core/mapping/SearchResourceMappings.java | 23 +++++++++- .../AbstractRepositoryRestController.java | 43 ++++++++++++++----- .../webmvc/RepositoryEntityController.java | 8 ++-- .../rest/webmvc/RepositoryLinksResource.java | 29 +++++++++---- ...RepositoryPropertyReferenceController.java | 10 +---- .../webmvc/RepositorySearchController.java | 25 +++++------ ...cessorHandlerMethodReturnValueHandler.java | 33 +++++++++----- .../data/rest/webmvc/CommonWebTests.java | 2 +- ...itorySearchControllerIntegrationTests.java | 2 +- ...dlerMethodReturnValueHandlerUnitTests.java | 36 ++++++++++++++++ ...yRepresentationConfigIntegrationTests.java | 6 +-- .../data/rest/webmvc/jpa/JpaWebTests.java | 6 ++- 13 files changed, 162 insertions(+), 63 deletions(-) diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java index 5a058a1d9..cb73ed8f4 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java @@ -50,7 +50,7 @@ public interface MethodResourceMapping extends ResourceMapping { /** * Returns the domain type that the query method returns. This will inspect wrapper types ({@link Collection}s, - * {@link Map}s, {@link Optional}s etc.) for their elemtn or value types. + * {@link Map}s, {@link Optional}s etc.) for their element or value types. * * @return will never be {@literal null}. * @since 2.3 diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java index 046c709f9..d0adfd913 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java @@ -111,7 +111,28 @@ public class SearchResourceMappings implements Iterable, for (MethodResourceMapping mapping : this) { - if (mapping.isExported() && mapping.getRel().endsWith(rel)) { + if (mapping.isExported() && mapping.getRel().equals(rel)) { + return mapping; + } + } + + return null; + } + + /** + * Returns the {@link MethodResourceMapping} for the given path. + * + * @param path must not be {@literal null} or empty. + * @return + * @since 2.4 + */ + public MethodResourceMapping getExportedMethodMappingForPath(String path) { + + Assert.hasText(path, "Path must not be null or empty!"); + + for (MethodResourceMapping mapping : this) { + + if (mapping.isExported() && mapping.getPath().matches(path)) { return mapping; } } 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 4f919cda9..09ae30e4d 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 @@ -18,6 +18,7 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.rest.webmvc.ControllerUtils.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.List; @@ -32,9 +33,11 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; +import org.springframework.hateoas.core.EmbeddedWrappers; import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; /** * @author Jon Brisbin @@ -44,6 +47,8 @@ import org.springframework.util.ClassUtils; @SuppressWarnings({ "rawtypes" }) class AbstractRepositoryRestController { + private static final EmbeddedWrappers WRAPPERS = new EmbeddedWrappers(false); + private final PagedResourcesAssembler pagedResourcesAssembler; private final AuditableBeanWrapperFactory auditableBeanWrapperFactory; @@ -75,13 +80,14 @@ class AbstractRepositoryRestController { } @SuppressWarnings({ "unchecked" }) - protected Resources toResources(Iterable source, PersistentEntityResourceAssembler assembler, Link baseLink) { + protected Resources toResources(Iterable source, PersistentEntityResourceAssembler assembler, + Class domainType, Link baseLink) { if (source instanceof Page) { Page page = (Page) source; - return entitiesToResources(page, assembler, baseLink); + return entitiesToResources(page, assembler, domainType, baseLink); } else if (source instanceof Iterable) { - return entitiesToResources((Iterable) source, assembler); + return entitiesToResources((Iterable) source, assembler, domainType); } else { return new Resources(EMPTY_RESOURCE_LIST); } @@ -93,13 +99,15 @@ class AbstractRepositoryRestController { * * @param source can be must not be {@literal null}. * @param assembler must not be {@literal null}. + * @param domainType the domain type in case the source is an empty iterable, must not be {@literal null}. * @param baseLink can be {@literal null}. * @return */ - protected Object toResource(Object source, PersistentEntityResourceAssembler assembler, Link baseLink) { + protected Object toResource(Object source, PersistentEntityResourceAssembler assembler, Class domainType, + Link baseLink) { if (source instanceof Iterable) { - return toResources((Iterable) source, assembler, baseLink); + return toResources((Iterable) source, assembler, domainType, baseLink); } else if (source == null) { throw new ResourceNotFoundException(); } else if (ClassUtils.isPrimitiveOrWrapper(source.getClass())) { @@ -109,14 +117,25 @@ class AbstractRepositoryRestController { return assembler.toFullResource(source); } - protected Resources> entitiesToResources(Page page, - PersistentEntityResourceAssembler assembler, Link baseLink) { + protected Resources entitiesToResources(Page page, PersistentEntityResourceAssembler assembler, + Class domainType, Link baseLink) { + + if (page.getContent().isEmpty()) { + return pagedResourcesAssembler.toEmptyResource(page, domainType, baseLink); + } + return baseLink == null ? pagedResourcesAssembler.toResource(page, assembler) : pagedResourcesAssembler.toResource( page, assembler, baseLink); } - protected Resources> entitiesToResources(Iterable entities, - PersistentEntityResourceAssembler assembler) { + protected Resources entitiesToResources(Iterable entities, PersistentEntityResourceAssembler assembler, + Class domainType) { + + if (!entities.iterator().hasNext()) { + + List content = Arrays. asList(WRAPPERS.emptyCollectionOf(domainType)); + return new Resources(content, getDefaultSelfLink()); + } List> resources = new ArrayList>(); @@ -124,7 +143,7 @@ class AbstractRepositoryRestController { resources.add(obj == null ? null : assembler.toResource(obj)); } - return new Resources>(resources); + return new Resources>(resources, getDefaultSelfLink()); } /** @@ -176,4 +195,8 @@ class AbstractRepositoryRestController { protected AuditableBeanWrapper getAuditableBeanWrapper(Object source) { return auditableBeanWrapperFactory.getBeanWrapperFor(source); } + + protected Link getDefaultSelfLink() { + return new Link(ServletUriComponentsBuilder.fromCurrentRequest().build().toUriString()); + } } 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 f526e23b8..f7522d186 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 @@ -170,9 +170,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem */ @ResponseBody @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) - public Resources getCollectionResource(final RootResourceInformation resourceInformation, - DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) - throws ResourceNotFoundException, HttpRequestMethodNotSupportedException { + public Resources getCollectionResource(RootResourceInformation resourceInformation, DefaultedPageable pageable, + Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException, + HttpRequestMethodNotSupportedException { resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION); @@ -202,7 +202,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem Link baseLink = entityLinks.linkToPagedResource(resourceInformation.getDomainType(), pageable.isDefault() ? null : pageable.getPageable()); - Resources result = toResources(results, assembler, baseLink); + Resources result = toResources(results, assembler, metadata.getDomainType(), baseLink); result.add(links); return result; } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryLinksResource.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryLinksResource.java index 97c881943..b0b4f085c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryLinksResource.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryLinksResource.java @@ -1,15 +1,26 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.rest.webmvc; -import java.util.Collections; - -import org.springframework.hateoas.Resources; +import org.springframework.hateoas.ResourceSupport; /** + * Dedicated resource type to represent the links pointing to collection resources exposed for repositories. + * * @author Jon Brisbin + * @author Oliver Gierke */ -public class RepositoryLinksResource extends Resources { - - public RepositoryLinksResource() { - super(Collections.emptyList()); - } -} +public class RepositoryLinksResource extends ResourceSupport {} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index da85404e9..a06d37956 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -107,7 +107,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, - @BackendId Serializable id, @PathVariable String property, final PersistentEntityResourceAssembler assembler) + @BackendId Serializable id, final @PathVariable String property, final PersistentEntityResourceAssembler assembler) throws Exception { final HttpHeaders headers = new HttpHeaders(); @@ -123,13 +123,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro if (prop.property.isCollectionLike()) { - List> resources = new ArrayList>(); - - for (Object obj : (Iterable) prop.propertyValue) { - resources.add(assembler.toResource(obj)); - } - - return new Resources>(resources); + return toResources((Iterable) prop.propertyValue, assembler, prop.propertyType, null); } else if (prop.property.isMap()) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java index f2b0c5bb5..9c60e3681 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.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. @@ -31,12 +31,12 @@ import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.Param; import org.springframework.data.repository.support.RepositoryInvoker; +import org.springframework.data.rest.core.mapping.MethodResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.mapping.SearchResourceMappings; import org.springframework.data.rest.webmvc.support.DefaultedPageable; import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks; -import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; @@ -74,8 +74,6 @@ class RepositorySearchController extends AbstractRepositoryRestController { private final RepositoryEntityLinks entityLinks; private final ResourceMappings mappings; - private final PagedResourcesAssembler assembler; - private final HateoasSortHandlerMethodArgumentResolver sortResolver; /** * Creates a new {@link RepositorySearchController} using the given {@link PagedResourcesAssembler}, @@ -84,22 +82,19 @@ class RepositorySearchController extends AbstractRepositoryRestController { * @param assembler must not be {@literal null}. * @param entityLinks must not be {@literal null}. * @param mappings must not be {@literal null}. + * @param auditableBeanWrapperFactory must not be {@literal null}. */ @Autowired public RepositorySearchController(PagedResourcesAssembler assembler, RepositoryEntityLinks entityLinks, - ResourceMappings mappings, HateoasSortHandlerMethodArgumentResolver sortResolver, - AuditableBeanWrapperFactory auditableBeanWrapperFactory) { + ResourceMappings mappings, AuditableBeanWrapperFactory auditableBeanWrapperFactory) { super(assembler, auditableBeanWrapperFactory); Assert.notNull(entityLinks, "EntityLinks must not be null!"); Assert.notNull(mappings, "ResourceMappings must not be null!"); - Assert.notNull(sortResolver, "HateoasSortHandlerMethodArgumentResolver must not be null!"); this.entityLinks = entityLinks; this.mappings = mappings; - this.assembler = assembler; - this.sortResolver = sortResolver; } /** @@ -155,6 +150,7 @@ class RepositorySearchController extends AbstractRepositoryRestController { RepositorySearchesResource result = new RepositorySearchesResource(resourceInformation.getDomainType()); result.add(queryMethodLinks); + result.add(getDefaultSelfLink()); return result; } @@ -180,7 +176,11 @@ class RepositorySearchController extends AbstractRepositoryRestController { Method method = checkExecutability(resourceInformation, search); Object result = executeQueryMethod(resourceInformation.getInvoker(), parameters, method, pageable, sort, assembler); - return new ResponseEntity(toResource(result, assembler, null), HttpStatus.OK); + SearchResourceMappings searchMappings = resourceInformation.getSearchMappings(); + MethodResourceMapping methodMapping = searchMappings.getExportedMethodMappingForPath(search); + Class domainType = methodMapping.getReturnedDomainType(); + + return new ResponseEntity(toResource(result, assembler, domainType, null), HttpStatus.OK); } /** @@ -189,7 +189,7 @@ class RepositorySearchController extends AbstractRepositoryRestController { * @param resourceInformation * @param parameters * @param repository - * @param searcg + * @param search * @param pageable * @param sort * @param assembler @@ -204,7 +204,8 @@ class RepositorySearchController extends AbstractRepositoryRestController { Method method = checkExecutability(resourceInformation, search); Object result = executeQueryMethod(resourceInformation.getInvoker(), parameters, method, pageable, sort, assembler); - Object resource = toResource(result, assembler, null); + ResourceMetadata metadata = resourceInformation.getResourceMetadata(); + Object resource = toResource(result, assembler, metadata.getDomainType(), null); List links = new ArrayList(); if (resource instanceof Resources && ((Resources) resource).getContent() != null) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandler.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandler.java index 210d4912e..c21cebfa6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandler.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandler.java @@ -19,6 +19,7 @@ import static org.springframework.data.util.ClassTypeInformation.*; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,6 +33,7 @@ import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceProcessor; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; +import org.springframework.hateoas.core.EmbeddedWrapper; import org.springframework.hateoas.mvc.HeaderLinksResponseEntity; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; @@ -292,7 +294,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler */ @Override public boolean supports(TypeInformation typeInformation, Object value) { - return targetType.isAssignableFrom(typeInformation); + return targetType.getType().isAssignableFrom(typeInformation.getType()); } /* @@ -352,7 +354,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler return false; } - return super.supports(typeInformation, value) || isValueTypeMatch((Resource) value, getTargetType()); + return super.supports(typeInformation, value) && isValueTypeMatch((Resource) value, getTargetType()); } /** @@ -408,7 +410,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler return false; } - return super.supports(typeInformation, value) || isValueTypeMatch((Resources) value, getTargetType()); + return super.supports(typeInformation, value) && isValueTypeMatch((Resources) value, getTargetType()); } /** @@ -431,22 +433,31 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler return false; } - Object element = content.iterator().next(); + TypeInformation superTypeInformation = null; - if (!(element instanceof Resource)) { - return false; + for (Class resourcesType : Arrays.> asList(resources.getClass(), Resources.class)) { + + superTypeInformation = target.getSuperTypeInformation(resourcesType); + + if (superTypeInformation != null) { + break; + } } - Class resourcesType = resources.getClass(); - - TypeInformation superTypeInformation = target.getSuperTypeInformation(resourcesType); - if (superTypeInformation == null) { return false; } + Object element = content.iterator().next(); TypeInformation resourceTypeInformation = superTypeInformation.getComponentType(); - return ResourceProcessorWrapper.isValueTypeMatch((Resource) element, resourceTypeInformation); + + if (element instanceof Resource) { + return ResourceProcessorWrapper.isValueTypeMatch((Resource) element, resourceTypeInformation); + } else if (element instanceof EmbeddedWrapper) { + return resourceTypeInformation.getType().isAssignableFrom(((EmbeddedWrapper) element).getRelTargetType()); + } + + return false; } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CommonWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CommonWebTests.java index 0fbc6049f..7ecf83a29 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CommonWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CommonWebTests.java @@ -119,7 +119,7 @@ public abstract class CommonWebTests extends AbstractWebIntegrationTests { Link searchLink = client.getDiscoverer(response).findLinkWithRel("search", rootResourceRepresentation); if (searchLink != null) { - client.request(searchLink); + client.follow(searchLink).andExpect(client.hasLinkWithRel("self")); } } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java index 436951aea..9e271cf06 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java @@ -71,7 +71,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll ResourceSupport resource = controller.listSearches(request); ResourceTester tester = ResourceTester.of(resource); - tester.assertNumberOfLinks(5); + tester.assertNumberOfLinks(6); // 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}"); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandlerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandlerUnitTests.java index dfa83d96c..27148e87d 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandlerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/ResourceProcessorHandlerMethodReturnValueHandlerUnitTests.java @@ -48,6 +48,7 @@ import org.springframework.hateoas.PagedResources.PageMetadata; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceProcessor; import org.springframework.hateoas.Resources; +import org.springframework.hateoas.core.EmbeddedWrappers; import org.springframework.hateoas.mvc.HeaderLinksResponseEntity; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; @@ -255,6 +256,23 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests { assertThat(ResourcesProcessorWrapper.isValueTypeMatch(pagedResources, type), is(false)); } + /** + * @see DATAREST-479 + */ + @Test + public void doesNotInvokeAProcessorForASpecializedType() throws Exception { + + EmbeddedWrappers wrappers = new EmbeddedWrappers(false); + Resources value = new Resources(Collections. singleton(wrappers + .emptyCollectionOf(Object.class))); + ResourcesProcessorWrapper wrapper = new ResourcesProcessorWrapper(new SpecialResourcesProcessor()); + + TypeInformation typeInformation = ClassTypeInformation.fromReturnTypeOf(Controller.class + .getMethod("resourcesOfObject")); + + assertThat(wrapper.supports(typeInformation, value), is(false)); + } + // Helpers ---------------------------------------------------------// private void invokeReturnValueHandler(String method, final Matcher matcher, Object returnValue) throws Exception { final MethodParameter methodParam = METHOD_PARAMS.get(method); @@ -353,6 +371,8 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests { ResponseEntity> resourceResponseEntity(); ResponseEntity> resourcesResponseEntity(); + + Resources resourcesOfObject(); } static class StringResource extends Resource { @@ -383,7 +403,23 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests { @Override public Resource process(Resource resource) { + this.invoked = true; + return resource; + } + } + static class SpecialResources extends Resources { + public SpecialResources() { + super(Collections.emptyList()); + } + } + + static class SpecialResourcesProcessor implements ResourceProcessor { + + boolean invoked = false; + + @Override + public SpecialResources process(SpecialResources resource) { this.invoked = true; return resource; } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/LegacyRepresentationConfigIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/LegacyRepresentationConfigIntegrationTests.java index 754ca0922..df4199314 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/LegacyRepresentationConfigIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/LegacyRepresentationConfigIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 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. @@ -50,7 +50,7 @@ public class LegacyRepresentationConfigIntegrationTests extends AbstractReposito public void returnsJsonIfConfiguredAndRequested() throws Exception { mvc.perform(get("/").accept(MediaType.APPLICATION_JSON)). // - andExpect(jsonPath("content", is(notNullValue()))); + andExpect(jsonPath("links", is(notNullValue()))); } /** @@ -60,6 +60,6 @@ public class LegacyRepresentationConfigIntegrationTests extends AbstractReposito public void returnsJsonIfConfigured() throws Exception { mvc.perform(get("/").accept(MediaType.ALL)). // - andExpect(jsonPath("content", is(notNullValue()))); + andExpect(jsonPath("links", is(notNullValue()))); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index 93e0e1cd3..eb78fdc00 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -576,11 +576,13 @@ public class JpaWebTests extends CommonWebTests { // Assert results returned as specified client.follow(findBySortedLink.expand("title,desc")).// andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).// - andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")); + andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).// + andExpect(client.hasLinkWithRel("self")); client.follow(findBySortedLink.expand("title,asc")).// andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).// - andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")); + andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).// + andExpect(client.hasLinkWithRel("self")); } /**