From 40645673bff8bfde734aa909d0387468f32b450a Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Mon, 27 Aug 2012 15:16:02 -0500 Subject: [PATCH] Integrated Spring HATEOAS. --- build.gradle | 4 +- gradle.properties | 1 + .../springframework/data/rest/core/Link.java | 28 -- .../data/rest/core/LinkList.java | 36 -- .../data/rest/core/MapResource.java | 33 -- .../data/rest/core/PostProcessor.java | 20 - .../data/rest/core/Resource.java | 90 ---- .../data/rest/core/ResourceLink.java | 79 ---- .../data/rest/core/ResourceSet.java | 101 ----- .../core/{Resolver.java => UriResolver.java} | 2 +- .../data/rest/core/util/UriUtils.java | 12 + spring-data-rest-repository/build.gradle | 3 + .../rest/repository/PageableResourceSet.java | 23 -- .../data/rest/repository/PagingMetadata.java | 3 + ...java => UriToDomainObjectUriResolver.java} | 10 +- .../AbstractRepositoryEventListener.java | 39 -- ...notatedHandlerRepositoryEventListener.java | 24 +- .../context/BeforeRenderResourceEvent.java | 13 - .../context/BeforeRenderResourcesEvent.java | 17 - .../rest/repository/context/RenderEvent.java | 55 --- .../invoke/RepositoryMethodResponse.java | 2 +- .../repository/spec/ExtensionsSpec.groovy | 54 --- spring-data-rest-webmvc/build.gradle | 3 +- .../data/rest/webmvc/EntityResource.java | 55 +++ ...itoryAwareMappingHttpMessageConverter.java | 40 +- .../webmvc/RepositoryRestConfiguration.java | 96 +---- .../rest/webmvc/RepositoryRestController.java | 384 +++++++----------- .../webmvc/RepositoryRestHandlerAdapter.java | 8 - .../RepositoryRestMvcConfiguration.java | 24 +- .../rest/webmvc/ResourcePostProcessor.java | 12 - ...cessorHandlerMethodReturnValueHandler.java | 2 +- .../rest/webmvc/ResourceSetPostProcessor.java | 13 - .../webmvc/ResourcesReturnValueHandler.java | 43 -- .../webmvc/UriListHttpMessageConverter.java | 67 +-- .../data/rest/webmvc/spec/EventsSpec.groovy | 4 - .../rest/webmvc/spec/QueryMethodsSpec.groovy | 2 +- .../webmvc/spec/ResourceProcessorSpec.groovy | 154 +++++++ .../rest/test/webmvc/ApplicationConfig.java | 16 - .../webmvc/LoggingResourcePostProcessor.java | 21 - .../rest/test/webmvc/PersonRepository.java | 7 +- .../spring-data-rest/repositories-export.xml | 7 - 41 files changed, 457 insertions(+), 1150 deletions(-) delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/MapResource.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java rename spring-data-rest-core/src/main/java/org/springframework/data/rest/core/{Resolver.java => UriResolver.java} (93%) delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PageableResourceSet.java rename spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/{UriToDomainObjectResolver.java => UriToDomainObjectUriResolver.java} (86%) delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeRenderResourceEvent.java delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeRenderResourcesEvent.java delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RenderEvent.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/EntityResource.java delete mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessor.java delete mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java delete mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java create mode 100644 spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/ResourceProcessorSpec.groovy delete mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java diff --git a/build.gradle b/build.gradle index 365d3a782..15a8b8e28 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ configure(subprojects) { subproject -> // Testing testCompile "org.spockframework:spock-core:$spockVersion" testCompile "org.spockframework:spock-spring:$spockVersion" - testCompile "org.hamcrest:hamcrest-library:1.2.1" + testCompile "org.hamcrest:hamcrest-library:1.3" testCompile "org.springframework:spring-test:$springVersion" testRuntime "org.springframework:spring-context-support:$springVersion" testCompile "org.mockito:mockito-core:1.8.5" @@ -96,6 +96,8 @@ idea { downloadSources = true } project { + jdkName = "OpenJDK 1.7" + languageLevel = "1.6" ipr { withXml { xml -> xml.node.component.find { it.@name == "VcsDirectoryMappings" }.mapping.@vcs = "Git" diff --git a/gradle.properties b/gradle.properties index cffdfa76b..9c6131a5f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,6 +12,7 @@ groovyVersion = 1.8.6 # Supporting libraries sdCommonsVersion = 1.4.0.BUILD-SNAPSHOT sdJpaVersion = 1.2.0.BUILD-SNAPSHOT +hateoasVersion = 0.3.0.BUILD-SNAPSHOT jacksonVersion = 1.9.7 hibernateVersion = 4.1.4.Final diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java deleted file mode 100644 index 8d9127dcd..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.springframework.data.rest.core; - -import java.net.URI; - -import org.codehaus.jackson.annotate.JsonTypeInfo; - -/** - * A simple bean representing a URI link. - * - * @author Jon Brisbin - */ -public interface Link { - - /** - * The text used in the {@literal rel} attribute. - * - * @return {@literal rel} attribute text. - */ - String rel(); - - /** - * The {@link URI} this link is referencing. - * - * @return {@link URI} of this link. Should not be null. - */ - URI href(); - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java deleted file mode 100644 index 0698958f6..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.springframework.data.rest.core; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simple abstraction for representing a list of {@link Link}s. - * - * @author Jon Brisbin - */ -public class LinkList { - - private List links = new ArrayList(); - - /** - * Add a {@link Link} to this list. - * - * @param link - * - * @return - */ - public LinkList add(Link link) { - links.add(link); - return this; - } - - /** - * Get the {@link Link}s in this list. - * - * @return - */ - public List getLinks() { - return this.links; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/MapResource.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/MapResource.java deleted file mode 100644 index 5f8edc267..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/MapResource.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.springframework.data.rest.core; - -import java.util.Map; - -import org.codehaus.jackson.annotate.JsonAnyGetter; -import org.codehaus.jackson.annotate.JsonIgnore; - -/** - * Abstraction that represents a Map-like REST resource plus a set of links. - * - * @author Jon Brisbin - */ -public class MapResource extends Resource> { - - public MapResource() { - } - - public MapResource(Map resource) { - this.resource = resource; - } - - @Override - @JsonIgnore - public Map getResource() { - return resource; - } - - @JsonAnyGetter - public Map any() { - return resource; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java deleted file mode 100644 index e986b1bc2..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.data.rest.core; - -import org.springframework.http.server.ServerHttpRequest; - -/** - * Implementations of this interface will post-process objects to mutate them in ways meaningful to the context in - * which they are called. - * - * @author Jon Brisbin - */ -public interface PostProcessor { - - /** - * Possibly perform some mutation on the object as a post-processing step in a flow. - * - * @param obj - */ - T postProcess(ServerHttpRequest request, T obj); - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java deleted file mode 100644 index b17b08c56..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.springframework.data.rest.core; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.annotate.JsonUnwrapped; -import org.springframework.util.Assert; - -/** - * Wraps a simple object or a bean plus a set of links. - * - * @author Jon Brisbin - */ -public class Resource { - - @JsonUnwrapped - protected T resource; - @JsonProperty("links") - protected Set links = new HashSet(); - - public Resource() { - } - - public Resource(T resource) { - this.resource = resource; - } - - /** - * Get the resource. May be {@literal null}. - * - * @return The resource or {@literal null}. - */ - public T getResource() { - return resource; - } - - /** - * Set the resource. - * - * @param resource - * - * @return {@literal this} - */ - public Resource setResource(T resource) { - this.resource = resource; - return this; - } - - /** - * Get the set of {@link Link}s for this resource. - * - * @return - */ - public Set getLinks() { - return links; - } - - /** - * Set the entire set of {@link Link}s. - * - * @param links - * - * @return {@literal this} - */ - @SuppressWarnings({"unchecked"}) - public Resource setLinks(Set links) { - if(null == links) { - this.links = Collections.emptySet(); - } else { - this.links = links; - } - return this; - } - - /** - * Add a {@link Link} to this resource's set. - * - * @param link - * - * @return {@literal this} - */ - public Resource addLink(Link link) { - Assert.notNull(link, "Link cannot be null."); - links.add(link); - return this; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java deleted file mode 100644 index e1549a945..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.springframework.data.rest.core; - -import java.net.URI; - -import org.codehaus.jackson.annotate.JsonProperty; -import org.springframework.util.Assert; - -/** - * Implementation of {@link Link}. - * - * @author Jon Brisbin - */ -public class ResourceLink implements Link, Comparable { - - @JsonProperty("rel") - private String rel; - @JsonProperty("href") - private URI href; - - public ResourceLink() { - } - - public ResourceLink(String rel, URI href) { - this.rel = rel; - this.href = href; - } - - @Override public String rel() { - return rel; - } - - @Override public URI href() { - return href; - } - - public String getRel() { - return rel(); - } - - public ResourceLink setRel(String rel) { - this.rel = rel; - return this; - } - - public URI getHref() { - return href(); - } - - public ResourceLink setHref(URI href) { - Assert.notNull(href, "href URI cannot be null."); - this.href = href; - return this; - } - - @Override public int compareTo(Link link) { - if(null == rel || null == link.rel()) { - return -1; - } - - int i = rel.compareTo(link.rel()); - if(i != 0) { - return i; - } - - if(null == href || null == link.href()) { - return -1; - } - - return (href.compareTo(link.href())); - } - - @Override public String toString() { - return "ResourceLink{" + - "rel='" + rel + '\'' + - ", href=" + href + - '}'; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java deleted file mode 100644 index e62024103..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.springframework.data.rest.core; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.codehaus.jackson.annotate.JsonProperty; -import org.springframework.util.Assert; - -/** - * Abstraction for representing resources to the user agent. - * - * @author Jon Brisbin - */ -@SuppressWarnings({"unchecked"}) -public class ResourceSet { - - @JsonProperty("content") - protected List> resources = new ArrayList>(); - @JsonProperty("links") - protected Set links = new HashSet(); - - /** - * Get the {@link Resource}s this {@literal ResourceSet} manages. - * - * @return - */ - public List> getResources() { - return resources; - } - - /** - * Set the {@link Resource}s this {@literal ResourceSet} manages. - * - * @param resources - * - * @return - */ - public ResourceSet setResources(List> resources) { - if(null == resources) { - this.resources = Collections.emptyList(); - } else { - this.resources = resources; - } - return this; - } - - /** - * Add a {@link Resource} to this set. - * - * @param resource - * - * @return {@literal this} - */ - public ResourceSet addResource(Resource resource) { - resources.add((null == resource ? new Resource() : resource)); - return this; - } - - /** - * Get the set of {@link Link}s. - * - * @return - */ - public Set getLinks() { - return links; - } - - /** - * Set the {@link Link}s this {@literal ResourceSet} manages. - * - * @param links - * - * @return {@literal this} - */ - @SuppressWarnings({"unchecked"}) - public ResourceSet setLinks(Set links) { - if(null == links) { - this.links = Collections.emptySet(); - } else { - this.links = links; - } - return this; - } - - /** - * Add a {@link Link} to this resource's set. - * - * @param link - * - * @return {@literal this} - */ - public ResourceSet addLink(Link link) { - Assert.notNull(link, "Link cannot be null."); - links.add(link); - return this; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resolver.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/UriResolver.java similarity index 93% rename from spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resolver.java rename to spring-data-rest-core/src/main/java/org/springframework/data/rest/core/UriResolver.java index 1dc6f3ae1..31500475f 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resolver.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/UriResolver.java @@ -7,7 +7,7 @@ import java.net.URI; * * @author Jon Brisbin */ -public interface Resolver { +public interface UriResolver { /** * Take a {@link URI} and resolve it to an actual object. diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java index d7ec4d2ad..a09439572 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java @@ -193,4 +193,16 @@ public abstract class UriUtils { return uris.size() > 0 ? uris.get(Math.max(uris.size() - 1, 0)) : null; } + /** + * Create a new {@link URI} out of the components. + * + * @param baseUri + * @param pathSegments + * + * @return + */ + public static URI buildUri(URI baseUri, String... pathSegments) { + return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri(); + } + } diff --git a/spring-data-rest-repository/build.gradle b/spring-data-rest-repository/build.gradle index 40eec5a77..66fefc81a 100644 --- a/spring-data-rest-repository/build.gradle +++ b/spring-data-rest-repository/build.gradle @@ -11,6 +11,9 @@ dependencies { //compile "org.springframework.data:spring-data-commons-core:$sdCommonsVersion" compile "org.springframework.data:spring-data-jpa:$sdJpaVersion" + // Spring HATEOAS + compile "org.springframework.hateoas:spring-hateoas:$hateoasVersion" + // Exporter core compile project(":spring-data-rest-core") diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PageableResourceSet.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PageableResourceSet.java deleted file mode 100644 index d392cef29..000000000 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PageableResourceSet.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.springframework.data.rest.repository; - -import org.codehaus.jackson.annotate.JsonProperty; -import org.springframework.data.rest.core.ResourceSet; - -/** - * @author Jon Brisbin - */ -public class PageableResourceSet extends ResourceSet { - - @JsonProperty("page") - protected PagingMetadata paging = new PagingMetadata(-1, 20, 0, 0); - - public PagingMetadata getPaging() { - return paging; - } - - public PageableResourceSet setPaging(PagingMetadata paging) { - this.paging = paging; - return this; - } - -} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PagingMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PagingMetadata.java index 8678cb3c1..795c6dc45 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PagingMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/PagingMetadata.java @@ -10,6 +10,9 @@ public class PagingMetadata { private int totalPages = 0; private long totalElements = 0; + public PagingMetadata() { + } + public PagingMetadata(int number, int size, int totalPages, diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectResolver.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectUriResolver.java similarity index 86% rename from spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectResolver.java rename to spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectUriResolver.java index 6dee130a0..f155a36f2 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectResolver.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriToDomainObjectUriResolver.java @@ -9,7 +9,7 @@ import java.util.Stack; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.rest.core.Resolver; +import org.springframework.data.rest.core.UriResolver; import org.springframework.data.rest.core.util.UriUtils; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.ClassUtils; @@ -17,9 +17,9 @@ import org.springframework.util.ClassUtils; /** * @author Jon Brisbin */ -public class UriToDomainObjectResolver - extends RepositoryExporterSupport - implements Resolver { +public class UriToDomainObjectUriResolver + extends RepositoryExporterSupport + implements UriResolver { @Autowired(required = false) private List conversionServices = Arrays.asList(new DefaultFormattingConversionService()); @@ -28,7 +28,7 @@ public class UriToDomainObjectResolver return conversionServices; } - public UriToDomainObjectResolver setConversionServices(List conversionServices) { + public UriToDomainObjectUriResolver setConversionServices(List conversionServices) { this.conversionServices = conversionServices; return this; } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java index 45218e268..75945d0bc 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java @@ -7,12 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; -import org.springframework.data.rest.core.Resource; -import org.springframework.data.rest.core.ResourceSet; import org.springframework.data.rest.repository.RepositoryExporter; import org.springframework.data.rest.repository.RepositoryExporterSupport; -import org.springframework.data.rest.repository.RepositoryMetadata; -import org.springframework.http.server.ServerHttpRequest; /** * Abstract class that listens for generic {@link RepositoryEvent}s and dispatches them to a specific @@ -54,13 +50,6 @@ public abstract class AbstractRepositoryEventListener> { + + public EntityResource(Map dto, Set links) { + super(dto, links); + } + + @SuppressWarnings({"unchecked"}) + public static EntityResource wrap(Object entity, RepositoryMetadata repoMeta, URI baseUri) { + + Set links = new HashSet(); + for(Object attrName : repoMeta.entityMetadata().linkedAttributes().keySet()) { + URI uri = buildUri(baseUri, attrName.toString()); + String rel = repoMeta.rel() + "." + entity.getClass().getSimpleName() + "." + attrName; + links.add(new Link(uri.toString(), rel)); + } + links.add(new Link(baseUri.toString(), "self")); + + Map entityDto = new HashMap(); + for(Map.Entry attrMeta : ((Map)repoMeta.entityMetadata() + .embeddedAttributes()).entrySet()) { + String name = attrMeta.getKey(); + Object val; + if(null != (val = attrMeta.getValue().get(entity))) { + entityDto.put(name, val); + } + } + + return new EntityResource(entityDto, links); + } + + @JsonAnyGetter + @Override public Map getContent() { + return super.getContent(); + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryAwareMappingHttpMessageConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryAwareMappingHttpMessageConverter.java index e5dd7ca91..aa4edc134 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryAwareMappingHttpMessageConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryAwareMappingHttpMessageConverter.java @@ -26,7 +26,6 @@ import org.codehaus.jackson.map.Module; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializerProvider; import org.codehaus.jackson.map.deser.std.StdDeserializer; -import org.codehaus.jackson.map.module.SimpleAbstractTypeResolver; import org.codehaus.jackson.map.module.SimpleDeserializers; import org.codehaus.jackson.map.module.SimpleKeyDeserializers; import org.codehaus.jackson.map.module.SimpleModule; @@ -34,14 +33,15 @@ import org.codehaus.jackson.map.module.SimpleSerializers; import org.codehaus.jackson.map.ser.std.SerializerBase; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.convert.ConversionService; -import org.springframework.data.rest.core.Link; -import org.springframework.data.rest.core.ResourceLink; import org.springframework.data.rest.repository.AttributeMetadata; import org.springframework.data.rest.repository.RepositoryExporter; import org.springframework.data.rest.repository.RepositoryMetadata; -import org.springframework.data.rest.repository.UriToDomainObjectResolver; +import org.springframework.data.rest.repository.UriToDomainObjectUriResolver; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.hateoas.Link; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -55,18 +55,19 @@ import org.springframework.web.util.UriComponentsBuilder; */ public class RepositoryAwareMappingHttpMessageConverter extends MappingJacksonHttpMessageConverter - implements InitializingBean { - - private final ObjectMapper mapper = new ObjectMapper(); + implements ApplicationEventPublisherAware, + InitializingBean { + private final ObjectMapper mapper = new ObjectMapper(); @Autowired(required = false) - protected List conversionServices = Arrays.asList(new DefaultFormattingConversionService()); + protected List conversionServices = Arrays.asList(new DefaultFormattingConversionService()); @Autowired(required = false) - protected List repositoryExporters = Collections.emptyList(); + protected List repositoryExporters = Collections.emptyList(); @Autowired(required = false) - protected List modules = Collections.emptyList(); + protected List modules = Collections.emptyList(); @Autowired - protected UriToDomainObjectResolver domainObjectResolver; + protected UriToDomainObjectUriResolver domainObjectResolver = null; + protected ApplicationEventPublisher eventPublisher = null; public RepositoryAwareMappingHttpMessageConverter() { setSupportedMediaTypes(Arrays.asList( @@ -77,6 +78,10 @@ public class RepositoryAwareMappingHttpMessageConverter setObjectMapper(mapper); } + @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + @Override public void afterPropertiesSet() throws Exception { mapper.registerModule(new RepositoryAwareModule()); for(Module m : modules) { @@ -114,11 +119,11 @@ public class RepositoryAwareMappingHttpMessageConverter return this; } - public UriToDomainObjectResolver getDomainObjectResolver() { + public UriToDomainObjectUriResolver getDomainObjectResolver() { return domainObjectResolver; } - public RepositoryAwareMappingHttpMessageConverter setDomainObjectResolver(UriToDomainObjectResolver domainObjectResolver) { + public RepositoryAwareMappingHttpMessageConverter setDomainObjectResolver(UriToDomainObjectUriResolver domainObjectResolver) { this.domainObjectResolver = domainObjectResolver; return this; } @@ -199,16 +204,11 @@ public class RepositoryAwareMappingHttpMessageConverter SimpleKeyDeserializers keyDsers = new SimpleKeyDeserializers(); private RepositoryAwareModule() { - super("RepositoryAwareModule", new Version(1, 0, 0, "SNAPSHOT")); + super("RepositoryAwareModule", Version.unknownVersion()); } @SuppressWarnings({"unchecked"}) @Override public void setupModule(SetupContext context) { - context.addAbstractTypeResolver( - new SimpleAbstractTypeResolver() - .addMapping(Link.class, ResourceLink.class) - ); - for(RepositoryExporter repoExp : repositoryExporters) { for(String repoName : new ArrayList(repoExp.repositoryNames())) { RepositoryMetadata repoMeta = repoExp.repositoryMetadataFor(repoName); @@ -272,7 +272,7 @@ public class RepositoryAwareMappingHttpMessageConverter String rel = repoMeta.rel() + "." + repoMeta.domainType().getSimpleName(); URI selfUri = buildUri(RepositoryRestController.BASE_URI.get(), repoMeta.name(), sId); - jgen.writeObject(new ResourceLink(rel, selfUri)); + jgen.writeObject(new Link(selfUri.toString(), rel)); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java index 4aa54919f..59e5a85c1 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java @@ -1,13 +1,9 @@ package org.springframework.data.rest.webmvc; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; @@ -22,18 +18,16 @@ public class RepositoryRestConfiguration { public static final RepositoryRestConfiguration DEFAULT = new RepositoryRestConfiguration(); - private int defaultPageSize = 20; - private String pageParamName = "page"; - private String limitParamName = "limit"; - private String sortParamName = "sort"; - private String jsonpParamName = "callback"; - private String jsonpOnErrParamName = null; - private List> customConverters = Collections.emptyList(); - private Multimap, ResourcePostProcessor> resourcePostProcessors = ArrayListMultimap.create(); - private List resourceSetPostProcessors = Collections.emptyList(); - private Map, Class> typeMappings = Collections.emptyMap(); - private MediaType defaultMediaType = MediaType.APPLICATION_JSON; - private boolean dumpErrors = true; + private int defaultPageSize = 20; + private String pageParamName = "page"; + private String limitParamName = "limit"; + private String sortParamName = "sort"; + private String jsonpParamName = "callback"; + private String jsonpOnErrParamName = null; + private List> customConverters = Collections.emptyList(); + private Map, Class> typeMappings = Collections.emptyMap(); + private MediaType defaultMediaType = MediaType.APPLICATION_JSON; + private boolean dumpErrors = true; /** * Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20. @@ -257,74 +251,4 @@ public class RepositoryRestConfiguration { return this; } - /** - * Get the list of {@link ResourceSetPostProcessor}s that will potentially alter the responses going back to the - * client. - * - * @return - */ - public List getResourceSetPostProcessors() { - return resourceSetPostProcessors; - } - - /** - * Set the list of {@link ResourceSetPostProcessor}s that will potentially alter the responses going back to the - * - * @param resourceSetPostProcessors - */ - @Autowired(required = false) - public RepositoryRestConfiguration setResourceSetPostProcessors(List resourceSetPostProcessors) { - Assert.notNull(resourceSetPostProcessors, "ResourceSetPostProcessors cannot be null."); - this.resourceSetPostProcessors = resourceSetPostProcessors; - return this; - } - - /** - * Add a {@link ResourcePostProcessor} that is responsible for post-processing a particular domain type. - * - * @param type - * @param postProcessor - * - * @return - */ - public RepositoryRestConfiguration addResourcePostProcessor(Class type, ResourcePostProcessor postProcessor) { - Assert.notNull(type, "Type for ResourcePostProcessor cannot be null."); - Assert.notNull(postProcessor, "ResourcePostProcessor for type " + type.getName() + " cannot be null."); - resourcePostProcessors.put(type, postProcessor); - return this; - } - - /** - * Set tje {@link ResourcePostProcessor} map used to determine what post-processors to run for which domain type. - * - * @param postProcessors - * - * @return - */ - public RepositoryRestConfiguration setResourcePostProcessors(Map, ResourcePostProcessor> postProcessors) { - if(null == postProcessors) { - return this; - } - for(Map.Entry, ResourcePostProcessor> entry : postProcessors.entrySet()) { - addResourcePostProcessor(entry.getKey(), entry.getValue()); - } - return this; - } - - /** - * Get the {@link ResourcePostProcessor}s assigned to a particular domain type. - * - * @param type - * - * @return - */ - public Collection getResourcePostProcessors(Class type) { - Collection pps = resourcePostProcessors.get(type); - if(null == pps) { - return Collections.emptyList(); - } else { - return pps; - } - } - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index a3d009b06..63b4dd48f 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -1,5 +1,7 @@ package org.springframework.data.rest.webmvc; +import static org.springframework.data.rest.core.util.UriUtils.*; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -45,23 +47,14 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; import org.springframework.data.rest.core.Handler; -import org.springframework.data.rest.core.Link; -import org.springframework.data.rest.core.LinkList; -import org.springframework.data.rest.core.MapResource; -import org.springframework.data.rest.core.Resource; -import org.springframework.data.rest.core.ResourceLink; -import org.springframework.data.rest.core.ResourceSet; import org.springframework.data.rest.core.convert.DelegatingConversionService; import org.springframework.data.rest.repository.AttributeMetadata; -import org.springframework.data.rest.repository.EntityMetadata; -import org.springframework.data.rest.repository.PageableResourceSet; -import org.springframework.data.rest.repository.PagingMetadata; import org.springframework.data.rest.repository.RepositoryConstraintViolationException; import org.springframework.data.rest.repository.RepositoryExporter; import org.springframework.data.rest.repository.RepositoryExporterSupport; import org.springframework.data.rest.repository.RepositoryMetadata; import org.springframework.data.rest.repository.RepositoryNotFoundException; -import org.springframework.data.rest.repository.UriToDomainObjectResolver; +import org.springframework.data.rest.repository.UriToDomainObjectUriResolver; import org.springframework.data.rest.repository.annotation.RestResource; import org.springframework.data.rest.repository.context.AfterDeleteEvent; import org.springframework.data.rest.repository.context.AfterLinkDeleteEvent; @@ -70,14 +63,16 @@ import org.springframework.data.rest.repository.context.AfterSaveEvent; import org.springframework.data.rest.repository.context.BeforeDeleteEvent; import org.springframework.data.rest.repository.context.BeforeLinkDeleteEvent; import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent; -import org.springframework.data.rest.repository.context.BeforeRenderResourceEvent; -import org.springframework.data.rest.repository.context.BeforeRenderResourcesEvent; import org.springframework.data.rest.repository.context.BeforeSaveEvent; import org.springframework.data.rest.repository.context.RepositoryEvent; import org.springframework.data.rest.repository.invoke.CrudMethod; import org.springframework.data.rest.repository.invoke.MethodParameterConversionService; import org.springframework.data.rest.repository.invoke.RepositoryQueryMethod; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.Resources; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpMethod; @@ -138,7 +133,8 @@ public class RepositoryRestController public static final String LOCATION = "Location"; public static final String SELF = "self"; final static ThreadLocal BASE_URI = new ThreadLocal(); - private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class); + private static final Logger LOG = LoggerFactory.getLogger( + RepositoryRestController.class); private static final TypeDescriptor STRING_ARRAY_TYPE = TypeDescriptor.valueOf(String[].class); /** @@ -163,7 +159,7 @@ public class RepositoryRestController private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT; private ObjectMapper objectMapper = new ObjectMapper(); private RepositoryAwareMappingHttpMessageConverter mappingHttpMessageConverter; - private UriToDomainObjectResolver domainObjectResolver; + private UriToDomainObjectUriResolver domainObjectResolver; private ApplicationContext applicationContext; { @@ -313,12 +309,12 @@ public class RepositoryRestController return this; } - public UriToDomainObjectResolver getDomainObjectResolver() { + public UriToDomainObjectUriResolver getDomainObjectResolver() { return domainObjectResolver; } @Autowired - public RepositoryRestController setDomainObjectResolver(UriToDomainObjectResolver domainObjectResolver) { + public RepositoryRestController setDomainObjectResolver(UriToDomainObjectUriResolver domainObjectResolver) { this.domainObjectResolver = domainObjectResolver; return this; } @@ -353,24 +349,20 @@ public class RepositoryRestController URI baseUri = uriBuilder.build().toUri(); BASE_URI.set(baseUri); - ResourceSet resources = new ResourceSet(); + List links = new ArrayList(); for(RepositoryExporter repoExporter : repositoryExporters) { for(String name : (Set)repoExporter.repositoryNames()) { RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor(name); String rel = repoMeta.rel(); URI path = buildUri(baseUri, name); - resources.addLink(new ResourceLink(rel, path)); + links.add(new Link(path.toString(), rel)); } } - // Publish an event that we're about to publish this ResourceSet - publishEvent(new BeforeRenderResourcesEvent(request, null, resources)); - // Run any configured post processors - for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { - resources = pp.postProcess(request, resources); - } - - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resources(Collections.emptyList(), links)); } /** @@ -405,21 +397,23 @@ public class RepositoryRestController return negotiateResponse(request, HttpStatus.METHOD_NOT_ALLOWED, new HttpHeaders(), null); } - Iterator allEntities = Collections.emptyList().iterator(); - ResourceSet resources; - if(repoMeta.repository() instanceof PagingAndSortingRepository) { - PageableResourceSet pr = new PageableResourceSet(); + Set links = new HashSet(); + PagedResources.PageMetadata pageMeta = null; + Iterator allEntities = Collections.emptyList().iterator(); + if(repoMeta.repository() instanceof PagingAndSortingRepository) { Page page = ((PagingAndSortingRepository)repoMeta.repository()).findAll(pageSort); if(page.hasContent()) { allEntities = page.iterator(); } // Set page counts in the response - pr.setPaging(new PagingMetadata(page.getNumber() + 1, - page.getSize(), - page.getTotalPages(), - page.getTotalElements())); + pageMeta = new PagedResources.PageMetadata( + page.getSize(), + page.getNumber() + 1, + page.getTotalElements(), + page.getTotalPages() + ); // Copy over parameters UriComponentsBuilder selfUri = UriComponentsBuilder.fromUri(baseUri).pathSegment(repository); @@ -439,7 +433,7 @@ public class RepositoryRestController !page.isFirstPage() && page.hasPreviousPage(), page.getNumber(), "prev", - pr.getLinks() + links ); maybeAddPrevNextLink( nextPrevBase, @@ -449,48 +443,39 @@ public class RepositoryRestController !page.isLastPage() && page.hasNextPage(), page.getNumber() + 2, "next", - pr.getLinks() + links ); - - resources = pr; } else { Iterable it = repoMeta.repository().findAll(); if(null != it) { allEntities = it.iterator(); } - resources = new ResourceSet(); } + List>> allResources = new ArrayList>>(); while(allEntities.hasNext()) { Object o = allEntities.next(); Serializable id = (Serializable)repoMeta.entityMetadata().idAttribute().get(o); if(shouldReturnLinks(request.getServletRequest().getHeader("Accept"))) { - resources.addLink(new ResourceLink(repoMeta.rel() + "." + o.getClass().getSimpleName(), - buildUri(baseUri, repository, id.toString()))); + links.add(new Link(buildUri(baseUri, repository, id.toString()).toString(), + repoMeta.rel() + "." + o.getClass().getSimpleName())); } else { URI selfUri = buildUri(baseUri, repository, id.toString()); - MapResource res = createResource(repoMeta.rel(), - o, - repoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - - resources.addResource(res); + allResources.add(EntityResource.wrap(o, repoMeta, selfUri)); } } if(!repoMeta.queryMethods().isEmpty()) { - resources.addLink(new ResourceLink(repoMeta.rel() + ".search", - buildUri(baseUri, repository, "search"))); + links.add(new Link(buildUri(baseUri, repository, "search").toString(), + repoMeta.rel() + ".search")); } - publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); - // Run any configured post processors - for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { - resources = pp.postProcess(request, resources); - } - - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + (null != pageMeta + ? new PagedResources(allResources, pageMeta, links) + : new Resources(allResources, links))); } /** @@ -517,7 +502,7 @@ public class RepositoryRestController BASE_URI.set(baseUri); RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - ResourceSet resources = new ResourceSet(); + Set links = new HashSet(); for(Map.Entry entry : ((Map)repoMeta.queryMethods()) .entrySet()) { @@ -527,29 +512,26 @@ public class RepositoryRestController Method m = entry.getValue().method(); if(m.isAnnotationPresent(RestResource.class)) { RestResource resourceAnno = m.getAnnotation(RestResource.class); - resources.addLink(new ResourceLink( - (StringUtils.hasText(resourceAnno.rel()) - ? repoMeta.rel() + "." + resourceAnno.rel() - : repoMeta.rel() + "." + entry.getKey()), + links.add(new Link( buildUri(baseSearchUri, (StringUtils.hasText(resourceAnno.path()) ? resourceAnno.path() - : entry.getKey())) + : entry.getKey())).toString(), + (StringUtils.hasText(resourceAnno.rel()) + ? repoMeta.rel() + "." + resourceAnno.rel() + : repoMeta.rel() + "." + entry.getKey()) )); } else { // No customizations, use the default - resources.addLink(new ResourceLink(repoMeta.rel() + "." + entry.getKey(), - buildUri(baseSearchUri, entry.getKey()))); + links.add(new Link(buildUri(baseSearchUri, entry.getKey()).toString(), + repoMeta.rel() + "." + entry.getKey())); } } - publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); - // Run any configured post processors - for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { - resources = pp.postProcess(request, resources); - } - - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resources(Collections.emptyList(), links)); } /** @@ -643,10 +625,15 @@ public class RepositoryRestController Object result; if(null == (result = queryMethod.method().invoke(repo, paramVals))) { - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), new ResourceSet()); + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resources(Collections.emptyList())); } - ResourceSet resources = new ResourceSet(); + Set links = new HashSet(); + PagedResources.PageMetadata pageMetadata = null; + Iterator entities = Collections.emptyList().iterator(); if(result instanceof Collection) { entities = ((Collection)result).iterator(); @@ -658,11 +645,10 @@ public class RepositoryRestController } // Set page counts in the response - PageableResourceSet pr = new PageableResourceSet(); - pr.setPaging(new PagingMetadata(page.getNumber() + 1, - page.getSize(), - page.getTotalPages(), - page.getTotalElements())); + pageMetadata = new PagedResources.PageMetadata(page.getSize(), + page.getNumber() + 1, + page.getTotalElements(), + page.getTotalPages()); // Copy over parameters UriComponentsBuilder selfUri = UriComponentsBuilder.fromUri(baseUri).pathSegment(repository, "search", query); @@ -682,7 +668,7 @@ public class RepositoryRestController !page.isFirstPage() && page.hasPreviousPage(), page.getNumber(), "prev", - pr.getLinks() + links ); maybeAddPrevNextLink( nextPrevBase, @@ -692,48 +678,41 @@ public class RepositoryRestController !page.isLastPage() && page.hasNextPage(), page.getNumber() + 2, "next", - pr.getLinks() + links ); - - resources = pr; } else { entities = Collections.singletonList(result).iterator(); } + List results = new ArrayList(); while(entities.hasNext()) { Object obj = entities.next(); - RepositoryMetadata elemRepoMeta; - if(null == (elemRepoMeta = repositoryMetadataFor(obj.getClass()))) { - resources.addResource(new Resource(obj)); + if(!hasRepositoryMetadataFor(obj.getClass())) { + results.add(obj); continue; } // This object is managed by a repository + RepositoryMetadata elemRepoMeta = repositoryMetadataFor(obj.getClass()); + String id = elemRepoMeta.entityMetadata().idAttribute().get(obj).toString(); if(shouldReturnLinks(request.getServletRequest().getHeader("Accept"))) { String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName(); URI path = buildUri(baseUri, repository, id); - resources.addLink(new ResourceLink(rel, path)); + links.add(new org.springframework.hateoas.Link(path.toString(), rel)); } else { URI selfUri = buildUri(baseUri, repository, id); - MapResource res = createResource(repoMeta.rel(), - obj, - repoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - - resources.addResource(res); + results.add(EntityResource.wrap(obj, repoMeta, selfUri)); } } - publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); - // Run any configured post processors - for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { - resources = pp.postProcess(request, resources); - } - - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + (null != pageMetadata + ? PagedResources.wrap(results, pageMetadata) + : Resources.wrap(results))); } /** @@ -788,19 +767,7 @@ public class RepositoryRestController Resource body = null; if(returnBody(request)) { - MapResource resource = createResource(repoMeta.rel(), - savedEntity, - repoMeta.entityMetadata(), - selfUri); - resource.addLink(new ResourceLink(SELF, selfUri)); - - body = resource; - - publishEvent(new BeforeRenderResourceEvent(request, repoMeta, body)); - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { - body = pp.postProcess(request, body); - } + body = EntityResource.wrap(savedEntity, repoMeta, selfUri); } return negotiateResponse(request, HttpStatus.CREATED, headers, body); @@ -860,19 +827,10 @@ public class RepositoryRestController } URI selfUri = buildUri(baseUri, repository, id); - Resource res = createResource(repoMeta.rel(), - entity, - repoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - - publishEvent(new BeforeRenderResourceEvent(request, repoMeta, res)); - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { - res = pp.postProcess(request, res); - } - - return negotiateResponse(request, HttpStatus.OK, headers, res); + return negotiateResponse(request, + HttpStatus.OK, + headers, + EntityResource.wrap(entity, repoMeta, selfUri)); } /** @@ -951,19 +909,7 @@ public class RepositoryRestController Object body = null; if(returnBody(request)) { - Resource res = createResource(repoMeta.rel(), - savedEntity, - repoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - - body = res; - - publishEvent(new BeforeRenderResourceEvent(request, repoMeta, body)); - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { - res = pp.postProcess(request, res); - } + body = EntityResource.wrap(savedEntity, repoMeta, selfUri); } if(!isUpdate) { @@ -1087,39 +1033,37 @@ public class RepositoryRestController return notFoundResponse(request); } - Object body; + Set links = new HashSet(); AttributeMetadata idAttr = propRepoMeta.entityMetadata().idAttribute(); String propertyRel = repository + "." + entity.getClass().getSimpleName() + "." + property; if(propVal instanceof Collection) { propertyRel += "." + propRepoMeta.entityMetadata().type().getSimpleName(); - ResourceSet resources = new ResourceSet(); + + List> outgoing = new ArrayList>(); for(Object o : (Collection)propVal) { String propValId = idAttr.get(o).toString(); URI path = buildUri(baseUri, repository, id, property, propValId); if(shouldReturnLinks(accept)) { - resources.addLink(new ResourceLink(propertyRel, path)); + links.add(new Link(path.toString(), propertyRel)); } else { URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId); - MapResource res = createResource(propRepoMeta.rel(), - o, - propRepoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - res.addLink(new ResourceLink(propertyRel, path)); - resources.addResource(res); + EntityResource er = EntityResource.wrap(o, propRepoMeta, selfUri); + er.add(new Link(path.toString(), propertyRel)); + outgoing.add(er); } } - body = resources; - // Run any post-processors for this domain type - for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { - resources = pp.postProcess(request, resources); - } + + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resources(outgoing, links)); + } else if(propVal instanceof Map) { propertyRel += "." + propRepoMeta.entityMetadata().type().getSimpleName(); - Map resource = new HashMap(); + Map resource = new HashMap(); for(Map.Entry entry : ((Map)propVal).entrySet()) { String propValId = idAttr.get(entry.getValue()).toString(); URI path = buildUri(baseUri, repository, id, property, propValId); @@ -1128,49 +1072,39 @@ public class RepositoryRestController String sKey = objectToMapKey(oKey); if(shouldReturnLinks(accept)) { - resource.put(sKey, new ResourceLink(propertyRel, path)); + resource.put(sKey, new Link(path.toString(), propertyRel)); } else { URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId); - Resource res = createResource(propRepoMeta.rel(), - entry.getValue(), - propRepoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(SELF, selfUri)); - res.addLink(new ResourceLink(propertyRel, path)); - resource.put(sKey, res); - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(propRepoMeta.domainType())) { - res = pp.postProcess(request, res); - } + EntityResource er = EntityResource.wrap(entry.getValue(), propRepoMeta, selfUri); + resource.put(sKey, er); } } - body = new MapResource(resource); + + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resource(resource, links)); + } else { String propValId = idAttr.get(propVal).toString(); URI path = buildUri(baseUri, repository, id, property); URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId); + + List> outgoing = new ArrayList>(); if(shouldReturnLinks(accept)) { - Resource resource = new Resource(); - resource.addLink(new ResourceLink(propertyRel, path)); - body = resource; + links.add(new Link(path.toString(), propertyRel)); } else { - MapResource res = createResource(propRepoMeta.rel(), - propVal, - propRepoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(propertyRel, path)); - res.addLink(new ResourceLink(SELF, selfUri)); - body = res; - } - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(propRepoMeta.domainType())) { - body = pp.postProcess(request, (Resource)body); + EntityResource er = EntityResource.wrap(propVal, propRepoMeta, selfUri); + outgoing.add(er); } + + return negotiateResponse(request, + HttpStatus.OK, + new HttpHeaders(), + new Resources(outgoing, links)); + } - publishEvent(new BeforeRenderResourceEvent(request, propRepoMeta, body)); - - return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), body); } /** @@ -1269,13 +1203,13 @@ public class RepositoryRestController }; MediaType incomingMediaType = request.getHeaders().getContentType(); - LinkList incomingLinks = readIncoming(request, + Resource incomingLinks = readIncoming(request, incomingMediaType, - LinkList.class); + Resource.class); for(Link l : incomingLinks.getLinks()) { Object o; - if(null != (o = domainObjectResolver.resolve(baseUri, URI.create(l.href().toString())))) { - rel.set(l.rel()); + if(null != (o = domainObjectResolver.resolve(baseUri, URI.create(l.getHref())))) { + rel.set(l.getRel()); ResponseEntity possibleResponse = entityHandler.handle(o); if(null != possibleResponse) { return possibleResponse; @@ -1429,23 +1363,14 @@ public class RepositoryRestController String propertyRel = repository + "." + repoMeta.entityMetadata().type().getSimpleName() + "." + property; URI propertyPath = buildUri(baseUri, repository, id, property, linkedId); URI selfUri = buildUri(baseUri, linkedRepoMeta.name(), linkedId); - Resource res = createResource(linkedRepoMeta.rel(), - linkedEntity, - linkedRepoMeta.entityMetadata(), - selfUri); - res.addLink(new ResourceLink(propertyRel, propertyPath)); - res.addLink(new ResourceLink(SELF, selfUri)); - publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, res)); - // Run any post-processors for this domain type - for(ResourcePostProcessor pp : config.getResourcePostProcessors(linkedRepoMeta.domainType())) { - res = pp.postProcess(request, res); - } + EntityResource er = EntityResource.wrap(linkedEntity, linkedRepoMeta, selfUri); + er.add(new Link(propertyPath.toString(), propertyRel)); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Location", selfUri.toString()); - return negotiateResponse(request, HttpStatus.OK, headers, res); + return negotiateResponse(request, HttpStatus.OK, headers, er); } /** @@ -1688,9 +1613,6 @@ public class RepositoryRestController Internal helper methods ----------------------------------- */ - private static URI buildUri(URI baseUri, String... pathSegments) { - return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri(); - } @SuppressWarnings({"unchecked"}) private void maybeAddPrevNextLink(URI resourceUri, @@ -1706,7 +1628,7 @@ public class RepositoryRestController urib.queryParam(config.getPageParamName(), nextPage); // PageRequest is 0-based, so it's already (page - 1) urib.queryParam(config.getLimitParamName(), page.getSize()); pageSort.addSortParameters(urib); - links.add(new ResourceLink(repoMeta.rel() + "." + rel, urib.build().toUri())); + links.add(new Link(urib.build().toUri().toString(), repoMeta.rel() + "." + rel)); } } @@ -1737,29 +1659,33 @@ public class RepositoryRestController return (V)mappingHttpMessageConverter.read(targetType, request); } - private MapResource createResource(String repoRel, - Object entity, - EntityMetadata entityMetadata, - URI baseUri) { - Map entityDto = new HashMap(); - MapResource resource = new MapResource(entityDto); - - for(Map.Entry attrMeta : entityMetadata.embeddedAttributes().entrySet()) { - String name = attrMeta.getKey(); - Object val; - if(null != (val = attrMeta.getValue().get(entity))) { - entityDto.put(name, val); - } - } - - for(String attrName : entityMetadata.linkedAttributes().keySet()) { - URI uri = buildUri(baseUri, attrName); - String rel = repoRel + "." + entity.getClass().getSimpleName() + "." + attrName; - resource.addLink(new ResourceLink(rel, uri)); - } - - return resource; - } + // private Set extractLinkedProperties(String repoRel, + // Object entity, + // EntityMetadata entityMetadata, + // URI baseUri) { + // Set links = new HashSet(); + // for(String attrName : entityMetadata.linkedAttributes().keySet()) { + // URI uri = buildUri(baseUri, attrName); + // String rel = repoRel + "." + entity.getClass().getSimpleName() + "." + attrName; + // links.add(new Link(uri.toString(), rel)); + // } + // return links; + // } + // + // private Map extractEmbeddedProperties(String repoRel, + // Object entity, + // EntityMetadata entityMetadata, + // URI baseUri) { + // Map entityDto = new HashMap(); + // for(Map.Entry attrMeta : entityMetadata.embeddedAttributes().entrySet()) { + // String name = attrMeta.getKey(); + // Object val; + // if(null != (val = attrMeta.getValue().get(entity))) { + // entityDto.put(name, val); + // } + // } + // return entityDto; + // } private boolean shouldReturnLinks(String acceptHeader) { if(null != acceptHeader) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java index b593305fb..9f135b2a3 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java @@ -2,10 +2,8 @@ package org.springframework.data.rest.webmvc; import java.util.Arrays; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.web.method.HandlerMethod; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; /** @@ -18,17 +16,11 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl */ public class RepositoryRestHandlerAdapter extends ResourceProcessorInvokingHandlerAdapter { - @Autowired - private ResourcesReturnValueHandler resourcesReturnValueHandler; - public RepositoryRestHandlerAdapter(RepositoryRestConfiguration config) { setCustomArgumentResolvers(Arrays.asList( new ServerHttpRequestMethodArgumentResolver(), new PagingAndSortingMethodArgumentResolver(config) )); - setCustomReturnValueHandlers(Arrays.asList( - resourcesReturnValueHandler - )); } @Override public int getOrder() { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index f688659e0..e55c13117 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; -import org.springframework.data.rest.repository.UriToDomainObjectResolver; +import org.springframework.data.rest.repository.UriToDomainObjectUriResolver; import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener; import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter; import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; @@ -44,7 +44,7 @@ public class RepositoryRestMvcConfiguration { * Main configuration for the REST exporter. */ @Autowired(required = false) - protected RepositoryRestConfiguration repositoryRestConfig; + protected RepositoryRestConfiguration repositoryRestConfig = RepositoryRestConfiguration.DEFAULT; /** * For getting access to the {@link javax.persistence.EntityManagerFactory}. @@ -62,7 +62,7 @@ public class RepositoryRestMvcConfiguration { */ @Bean public JpaRepositoryExporter jpaRepositoryExporter() { return (null == customJpaRepositoryExporter - ? new JpaRepositoryExporter().setDomainTypeMappings(repositoryRestConfiguration().getDomainTypeToRepositoryMappings()) + ? new JpaRepositoryExporter().setDomainTypeMappings(repositoryRestConfig.getDomainTypeToRepositoryMappings()) : customJpaRepositoryExporter); } @@ -88,20 +88,14 @@ public class RepositoryRestMvcConfiguration { } /** - * A {@link org.springframework.data.rest.core.Resolver} implementation that takes a {@link java.net.URI} and turns + * A {@link org.springframework.data.rest.core.UriResolver} implementation that takes a {@link java.net.URI} and turns * it * into a top-level domain object. * * @return */ - @Bean public UriToDomainObjectResolver domainObjectResolver() { - return new UriToDomainObjectResolver(); - } - - @Bean public RepositoryRestConfiguration repositoryRestConfiguration() { - return (null == repositoryRestConfig - ? RepositoryRestConfiguration.DEFAULT - : repositoryRestConfig); + @Bean public UriToDomainObjectUriResolver domainObjectResolver() { + return new UriToDomainObjectUriResolver(); } /** @@ -115,10 +109,6 @@ public class RepositoryRestMvcConfiguration { return new RepositoryRestController(); } - @Bean ResourcesReturnValueHandler resourcesReturnValueHandler() { - return new ResourcesReturnValueHandler(repositoryRestConfiguration()); - } - /** * Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in the * {@link RepositoryRestController} class. @@ -126,7 +116,7 @@ public class RepositoryRestMvcConfiguration { * @return */ @Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() { - return new RepositoryRestHandlerAdapter(repositoryRestConfiguration()); + return new RepositoryRestHandlerAdapter(repositoryRestConfig); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessor.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessor.java deleted file mode 100644 index 222ec7cca..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import org.springframework.data.rest.core.PostProcessor; -import org.springframework.data.rest.core.Resource; - -/** - * Implementations of this interface are allowed to mutate a {@link Resource} being sent back to the client. - * - * @author Jon Brisbin - */ -public interface ResourcePostProcessor extends PostProcessor { -} 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 377a27a64..17946dd66 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 @@ -425,7 +425,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler } /** - * Helper extension of {@link AnnotationAwareOrderComparator} to make {@link #getOrder()} public to allow it being + * Helper extension of {@link AnnotationAwareOrderComparator} to make {@link #getOrder(Object)} public to allow it being * used in a standalone fashion. * * @author Oliver Gierke diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java deleted file mode 100644 index 98e8da043..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import org.springframework.data.rest.core.PostProcessor; -import org.springframework.data.rest.core.ResourceSet; - -/** - * Implementations of this interface are allowed to mutate the {@link org.springframework.data.rest.core.ResourceSet} - * object that is being sent back to the client as a response. - * - * @author Jon Brisbin - */ -public interface ResourceSetPostProcessor extends PostProcessor { -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java deleted file mode 100644 index 29e304a8b..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import org.springframework.core.MethodParameter; -import org.springframework.data.rest.core.ResourceSet; -import org.springframework.data.rest.repository.RepositoryExporterSupport; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.method.support.ModelAndViewContainer; - -/** - * {@link HandlerMethodReturnValueHandler} implementation that applies user-defined post-processors to the - * representation being sent back to the client. - * - * @author Jon Brisbin - */ -public class ResourcesReturnValueHandler - extends RepositoryExporterSupport - implements HandlerMethodReturnValueHandler { - - private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT; - - public ResourcesReturnValueHandler(RepositoryRestConfiguration config) { - if(null != config) { - this.config = config; - } else { - this.config = config; - } - } - - @Override public boolean supportsReturnType(MethodParameter returnType) { - return ResourceSet.class.isAssignableFrom(returnType.getParameterType()); - } - - @Override - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest) throws Exception { - ResourceSet resources = (ResourceSet)returnValue; - - } - -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java index bb3309a75..7b9b18140 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java @@ -5,21 +5,19 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; -import org.springframework.data.rest.core.Link; -import org.springframework.data.rest.core.LinkList; -import org.springframework.data.rest.core.Resource; -import org.springframework.data.rest.core.ResourceLink; import org.springframework.data.rest.repository.invoke.RepositoryMethodResponse; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.Assert; /** * A special {@link org.springframework.http.converter.HttpMessageConverter} that can take various input formats and @@ -35,51 +33,31 @@ public class UriListHttpMessageConverter extends AbstractHttpMessageConverter clazz) { return (RepositoryMethodResponse.class.isAssignableFrom(clazz) - || List.class.isAssignableFrom(clazz) - || Map.class.isAssignableFrom(clazz) - || LinkList.class.isAssignableFrom(clazz) - || Resource.class.isAssignableFrom(clazz)); + || Resource.class.isAssignableFrom(clazz) + || Set.class.isAssignableFrom(clazz)); } @SuppressWarnings({"unchecked"}) @Override protected Object readInternal(Class clazz, - HttpInputMessage inputMessage) - throws IOException, - HttpMessageNotReadableException { + HttpInputMessage inputMessage) throws IOException, + HttpMessageNotReadableException { + Assert.isTrue((Resource.class.isAssignableFrom(clazz) || Set.class.isAssignableFrom(clazz)), + "Cannot read a text/uri-list into a " + clazz); String rel = inputMessage.getHeaders().getFirst("x-spring-data-urilist-rel"); if(null == rel && inputMessage instanceof ServletServerHttpRequest) { rel = ((ServletServerHttpRequest)inputMessage).getURI().getPath().substring(1).replaceAll("/", "."); } BufferedReader reader = new BufferedReader(new InputStreamReader(inputMessage.getBody())); + + Set links = new HashSet(); String line; - Object links; - try { - links = clazz.newInstance(); - } catch(InstantiationException e) { - throw new HttpMessageNotReadableException(e.getMessage(), e); - } catch(IllegalAccessException e) { - throw new HttpMessageNotReadableException(e.getMessage(), e); - } while(null != (line = reader.readLine())) { - Link l = new ResourceLink(rel, URI.create(line.trim())); - if(links instanceof LinkList) { - ((LinkList)links).add(l); - } else if(links instanceof List) { - ((List)links).add(l); - } else if(links instanceof Map) { - List linksFromMap = (List)((Map)links).get("links"); - if(null == linksFromMap) { - linksFromMap = new ArrayList(); - ((Map)links).put("links", linksFromMap); - } - linksFromMap.add(l); - } else if(links instanceof Resource) { - ((Resource)links).addLink(l); - } + links.add(new Link(URI.create(line.trim()).toString(), rel)); } - return links; + + return (Set.class.isAssignableFrom(clazz) ? links : new Resource("", links)); } @Override @@ -87,22 +65,15 @@ public class UriListHttpMessageConverter extends AbstractHttpMessageConverter> processors = [] + boolean handleReturnValueCalled + HandlerMethodReturnValueHandler resourceHandler + + def setup() { + delegateHandler = Mock(HandlerMethodReturnValueHandler) + delegateHandler.handleReturnValue(_, _, null, null) >> { handleReturnValueCalled = true } + + processors << new SpecialStringResourceProcessor() << + new SpecialLongResourceProcessor() << + new StringResourceProcessor() << + new LongResourceProcessor() + + resourceHandler = new ResourceProcessorHandlerMethodReturnValueHandler(delegateHandler, processors) + } + + Resource createStringResource() { + new Resource("string-resource") + } + + Resource createLongResource() { + new Resource(1L) + } + + StringResource createSpecialStringResource() { + new StringResource("special-string-resource") + } + + LongResource createSpecialLongResource() { + new LongResource(1L) + } + + def "processes simple String resource"() { + + given: + def resource = createStringResource() + + when: + resourceHandler.handleReturnValue(resource, STRING_RESOURCE_PARAM, null, null) + + then: + null != resource.getLink("string-resource") + handleReturnValueCalled + + } + + def "process simple Long resource"() { + + given: + def resource = createLongResource() + + when: + resourceHandler.handleReturnValue(resource, LONG_RESOURCE_PARAM, null, null) + + then: + null != resource.getLink("long-resource") + handleReturnValueCalled + + } + + def "process specialized String resource"() { + + given: + def resource = createSpecialStringResource() + + when: + resourceHandler.handleReturnValue(resource, SPECIAL_STRING_RESOURCE_PARAM, null, null) + + then: + null != resource.getLink("special-string-resource") + handleReturnValueCalled + + } + + def "process specialized Long resource"() { + + given: + def resource = createSpecialLongResource() + + when: + resourceHandler.handleReturnValue(resource, SPECIAL_LONG_RESOURCE_PARAM, null, null) + + then: + null != resource.getLink("special-long-resource") + handleReturnValueCalled + + } + +} + +class StringResourceProcessor implements ResourceProcessor> { + @Override Resource process(Resource resource) { + resource.add(new Link("http://localhost:8080/string-resource", "string-resource")) + resource + } +} + +class LongResourceProcessor implements ResourceProcessor> { + @Override Resource process(Resource resource) { + resource.add(new Link("http://localhost:8080/long-resource", "long-resource")) + resource + } +} + +class StringResource extends Resource { + StringResource(String content, Link... links) { + super(content, links) + } +} + +class SpecialStringResourceProcessor implements ResourceProcessor { + @Override StringResource process(StringResource resource) { + resource.add(new Link("http://localhost:8080/special-string-resource", "special-string-resource")) + resource + } +} + +class LongResource extends Resource { + LongResource(Long content, Link... links) { + super(content, links) + } +} + +class SpecialLongResourceProcessor implements ResourceProcessor { + @Override LongResource process(LongResource resource) { + resource.add(new Link("http://localhost:8080/special-long-resource", "special-long-resource")) + resource + } +} \ No newline at end of file diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java index 986e87a59..6aaa98282 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java @@ -1,9 +1,7 @@ package org.springframework.data.rest.test.webmvc; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @@ -14,11 +12,8 @@ import org.springframework.context.annotation.Import; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.data.rest.core.Resource; import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration; -import org.springframework.data.rest.webmvc.ResourcePostProcessor; import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.http.server.ServerHttpRequest; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.jpa.JpaDialect; @@ -88,15 +83,4 @@ public class ApplicationConfig { return cs; } - @Bean public Map, ResourcePostProcessor> resourcePostProcessors() { - Map, ResourcePostProcessor> resourcePostProcessors = new HashMap, ResourcePostProcessor>(); - resourcePostProcessors.put(Person.class, new ResourcePostProcessor() { - @Override public Resource postProcess(ServerHttpRequest request, Resource r) { - System.out.println(" **** post-processing request: " + request + " with resource: " + r); - return r; - } - }); - return resourcePostProcessors; - } - } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java deleted file mode 100644 index 6e6666f12..000000000 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.springframework.data.rest.test.webmvc; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.rest.core.Resource; -import org.springframework.data.rest.webmvc.ResourcePostProcessor; -import org.springframework.http.server.ServerHttpRequest; - -/** - * @author Jon Brisbin - */ -public class LoggingResourcePostProcessor implements ResourcePostProcessor { - - private Logger logger = LoggerFactory.getLogger(LoggingResourcePostProcessor.class); - - @Override public Resource postProcess(ServerHttpRequest request, Resource r) { - logger.info(" **** post-processing request: " + request + " with resource: " + r); - return r; - } - -} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java index 555669e1d..049ad9976 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java @@ -7,7 +7,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.repository.annotation.ConvertWith; import org.springframework.data.rest.repository.annotation.RestResource; /** @@ -30,8 +29,10 @@ public interface PersonRepository extends PagingAndSortingRepository findById(@Param("ids") List ids, Pageable pageable); + @RestResource(path = "id") Page findById(@Param("ids") List ids, Pageable pageable); } diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml index 58e676a8c..c5842140a 100644 --- a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml @@ -15,13 +15,6 @@ value="org.springframework.data.rest.test.webmvc.PersonRepository"/> - - - - - - -