Integrated Spring HATEOAS.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <jbrisbin@vmware.com>
|
||||
*/
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -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<Link> links = new ArrayList<Link>();
|
||||
|
||||
/**
|
||||
* 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<Link> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Map<String, Object>> {
|
||||
|
||||
public MapResource() {
|
||||
}
|
||||
|
||||
public MapResource(Map<String, Object> resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Map<String, Object> getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<T> {
|
||||
|
||||
/**
|
||||
* Possibly perform some mutation on the object as a post-processing step in a flow.
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
T postProcess(ServerHttpRequest request, T obj);
|
||||
|
||||
}
|
||||
@@ -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<T> {
|
||||
|
||||
@JsonUnwrapped
|
||||
protected T resource;
|
||||
@JsonProperty("links")
|
||||
protected Set<Link> links = new HashSet<Link>();
|
||||
|
||||
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<T> setResource(T resource) {
|
||||
this.resource = resource;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of {@link Link}s for this resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Link> getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entire set of {@link Link}s.
|
||||
*
|
||||
* @param links
|
||||
*
|
||||
* @return {@literal this}
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Resource<T> setLinks(Set<Link> 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<T> addLink(Link link) {
|
||||
Assert.notNull(link, "Link cannot be null.");
|
||||
links.add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Link> {
|
||||
|
||||
@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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Resource<?>> resources = new ArrayList<Resource<?>>();
|
||||
@JsonProperty("links")
|
||||
protected Set<Link> links = new HashSet<Link>();
|
||||
|
||||
/**
|
||||
* Get the {@link Resource}s this {@literal ResourceSet} manages.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Resource<?>> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Resource}s this {@literal ResourceSet} manages.
|
||||
*
|
||||
* @param resources
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ResourceSet setResources(List<Resource<?>> 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<Object>() : resource));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of {@link Link}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Link> getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Link}s this {@literal ResourceSet} manages.
|
||||
*
|
||||
* @param links
|
||||
*
|
||||
* @return {@literal this}
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public ResourceSet setLinks(Set<Link> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import java.net.URI;
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public interface Resolver<T> {
|
||||
public interface UriResolver<T> {
|
||||
|
||||
/**
|
||||
* Take a {@link URI} and resolve it to an actual object.
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<UriToDomainObjectResolver>
|
||||
implements Resolver<Object> {
|
||||
public class UriToDomainObjectUriResolver
|
||||
extends RepositoryExporterSupport<UriToDomainObjectUriResolver>
|
||||
implements UriResolver<Object> {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<ConversionService> conversionServices = Arrays.<ConversionService>asList(new DefaultFormattingConversionService());
|
||||
@@ -28,7 +28,7 @@ public class UriToDomainObjectResolver
|
||||
return conversionServices;
|
||||
}
|
||||
|
||||
public UriToDomainObjectResolver setConversionServices(List<ConversionService> conversionServices) {
|
||||
public UriToDomainObjectUriResolver setConversionServices(List<ConversionService> conversionServices) {
|
||||
this.conversionServices = conversionServices;
|
||||
return this;
|
||||
}
|
||||
@@ -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<T extends AbstractReposito
|
||||
onBeforeDelete(event.getSource());
|
||||
} else if(event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete(event.getSource());
|
||||
} else if(event instanceof RenderEvent) {
|
||||
RenderEvent ev = (RenderEvent)event;
|
||||
if(ev.isTopLevelResource()) {
|
||||
onBeforeRenderResources(ev.getRequest(), ev.getRepositoryMetadata(), ev.getResources());
|
||||
} else {
|
||||
onBeforeRenderResource(ev.getRequest(), ev.getRepositoryMetadata(), ev.getResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,32 +121,4 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
protected void onAfterDelete(Object entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeRender} for top-level events. These are events
|
||||
* triggered by the exporter before sending out a wrapped, top-level response for queries, entity lists, and results
|
||||
* that are pagable.
|
||||
*
|
||||
* @param request
|
||||
* @param repositoryMetadata
|
||||
* @param resources
|
||||
*/
|
||||
protected void onBeforeRenderResources(ServerHttpRequest request,
|
||||
RepositoryMetadata repositoryMetadata,
|
||||
ResourceSet resources) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeRender} for entity events. These are events emitted
|
||||
* by the exporter before sending out an entity representation to the client. These events are triggered when
|
||||
* requesting individual entities and specific properties of an entity.
|
||||
*
|
||||
* @param request
|
||||
* @param repositoryMetadata
|
||||
* @param resource
|
||||
*/
|
||||
protected void onBeforeRenderResource(ServerHttpRequest request,
|
||||
RepositoryMetadata repositoryMetadata,
|
||||
Resource resource) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeRenderResource;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeRenderResources;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -122,16 +120,6 @@ public class AnnotatedHandlerRepositoryEventListener implements ApplicationListe
|
||||
inspect(targetType, handler, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleBeforeDelete.class, BeforeDeleteEvent.class);
|
||||
inspect(targetType, handler, method, HandleAfterDelete.class, AfterDeleteEvent.class);
|
||||
inspect(targetType,
|
||||
handler,
|
||||
method,
|
||||
HandleBeforeRenderResource.class,
|
||||
BeforeRenderResourceEvent.class);
|
||||
inspect(targetType,
|
||||
handler,
|
||||
method,
|
||||
HandleBeforeRenderResources.class,
|
||||
BeforeRenderResourcesEvent.class);
|
||||
}
|
||||
},
|
||||
new ReflectionUtils.MethodFilter() {
|
||||
@@ -158,13 +146,7 @@ public class AnnotatedHandlerRepositoryEventListener implements ApplicationListe
|
||||
try {
|
||||
Object src = event.getSource();
|
||||
|
||||
if(event instanceof RenderEvent) {
|
||||
RenderEvent ev = (RenderEvent)event;
|
||||
if(!ClassUtils.isAssignable(handlerMethod.targetType,
|
||||
ev.getRepositoryMetadata().entityMetadata().type())) {
|
||||
continue;
|
||||
}
|
||||
} else if(!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
if(!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -174,10 +156,6 @@ public class AnnotatedHandlerRepositoryEventListener implements ApplicationListe
|
||||
params.add(((BeforeLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof AfterLinkSaveEvent) {
|
||||
params.add(((AfterLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof RenderEvent) {
|
||||
RenderEvent ev = (RenderEvent)event;
|
||||
params.add(0, ev.getRequest());
|
||||
params.add(1, ev.getRepositoryMetadata());
|
||||
}
|
||||
|
||||
handlerMethod.method.invoke(handlerMethod.handler, params.toArray());
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class BeforeRenderResourceEvent extends RenderEvent {
|
||||
public BeforeRenderResourceEvent(ServerHttpRequest request, RepositoryMetadata repoMeta, Object source) {
|
||||
super(request, repoMeta, source);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* Event emitted before the the object is rendered to the client. Implementations of {@link
|
||||
* AbstractRepositoryEventListener} can listen for these events and alter the output of the resource being sent to the
|
||||
* link.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class BeforeRenderResourcesEvent extends RenderEvent {
|
||||
public BeforeRenderResourcesEvent(ServerHttpRequest request, RepositoryMetadata repoMeta, Object source) {
|
||||
super(request, repoMeta, source);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.data.rest.core.Resource;
|
||||
import org.springframework.data.rest.core.ResourceSet;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class RenderEvent extends RepositoryEvent {
|
||||
|
||||
protected final ServerHttpRequest request;
|
||||
protected final RepositoryMetadata repositoryMetadata;
|
||||
protected final boolean topLevelResource;
|
||||
|
||||
public RenderEvent(ServerHttpRequest request, RepositoryMetadata repoMeta, Object source) {
|
||||
super(source);
|
||||
Assert.isTrue(source instanceof Resource || source instanceof ResourceSet,
|
||||
"Event source must be of type 'Resource' or 'ResourceSet'");
|
||||
this.request = request;
|
||||
this.repositoryMetadata = repoMeta;
|
||||
this.topLevelResource = (source instanceof ResourceSet);
|
||||
}
|
||||
|
||||
public ServerHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public Resource getResource() {
|
||||
if(getSource() instanceof Resource) {
|
||||
return (Resource)getSource();
|
||||
} else {
|
||||
throw new IllegalStateException("Source of event is not a Resource, it's " + source);
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceSet getResources() {
|
||||
if(getSource() instanceof ResourceSet) {
|
||||
return (ResourceSet)getSource();
|
||||
} else {
|
||||
throw new IllegalStateException("Source of event is not a Resources, it's " + source);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTopLevelResource() {
|
||||
return topLevelResource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.hateoas.Link;
|
||||
|
||||
/**
|
||||
* JSON-serializable response for returns that have a mix of results and links. Also used in responses that have just
|
||||
|
||||
@@ -4,18 +4,12 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.rest.core.Resource
|
||||
import org.springframework.data.rest.core.ResourceSet
|
||||
import org.springframework.data.rest.core.ResourceLink
|
||||
import org.springframework.data.rest.repository.RepositoryExporter
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterDelete
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeRenderResource
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeRenderResources
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler
|
||||
import org.springframework.data.rest.repository.context.AfterDeleteEvent
|
||||
@@ -24,12 +18,9 @@ import org.springframework.data.rest.repository.context.AfterSaveEvent
|
||||
import org.springframework.data.rest.repository.context.AnnotatedHandlerRepositoryEventListener
|
||||
import org.springframework.data.rest.repository.context.BeforeDeleteEvent
|
||||
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.test.ApplicationConfig
|
||||
import org.springframework.data.rest.repository.test.Person
|
||||
import org.springframework.http.server.ServerHttpRequest
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -69,31 +60,6 @@ class ExtensionsSpec extends Specification {
|
||||
|
||||
}
|
||||
|
||||
def "responds to render events"() {
|
||||
|
||||
given:
|
||||
def repoMeta = exporter.repositoryMetadataFor(Person)
|
||||
def request = Mock(ServerHttpRequest)
|
||||
|
||||
def p = new Person("John Doe")
|
||||
def selfLink = new ResourceLink("self", new URI("http://localhost:8080/people/1"))
|
||||
def resources = new ResourceSet()
|
||||
resources.links << selfLink
|
||||
def resource = new Resource(p)
|
||||
resource.links << selfLink
|
||||
|
||||
when:
|
||||
appCtx.publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources))
|
||||
appCtx.publishEvent(new BeforeRenderResourceEvent(request, repoMeta, resource))
|
||||
|
||||
then:
|
||||
resources.links.size() == 2
|
||||
null != resources.links.find { it.rel() == "linkAddedByHandler" }
|
||||
resource.links.size() == 2
|
||||
null != resources.links.find { it.rel() == "linkAddedByHandler" }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -107,10 +73,6 @@ class EventsApplicationConfig {
|
||||
new PersonEventHandler()
|
||||
}
|
||||
|
||||
@Bean PersonRenderHandler personRenderHandler() {
|
||||
new PersonRenderHandler()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RepositoryEventHandler(Person)
|
||||
@@ -149,19 +111,3 @@ class PersonEventHandler {
|
||||
|
||||
}
|
||||
|
||||
@RepositoryEventHandler(Person)
|
||||
class PersonRenderHandler {
|
||||
|
||||
@HandleBeforeRenderResources void handleBeforeRenderResources(ServerHttpRequest request,
|
||||
RepositoryMetadata repoMeta,
|
||||
ResourceSet resources) {
|
||||
resources.links << new ResourceLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
|
||||
}
|
||||
|
||||
@HandleBeforeRenderResource void handleBeforeRenderResource(ServerHttpRequest request,
|
||||
RepositoryMetadata repoMeta,
|
||||
Resource resource) {
|
||||
resource.links << new ResourceLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ dependencies {
|
||||
|
||||
// Repository Exporter support
|
||||
compile project(":spring-data-rest-repository")
|
||||
|
||||
compile "org.springframework.hateoas:spring-hateoas:0.3.0.BUILD-SNAPSHOT"
|
||||
|
||||
compile "org.springframework.data:spring-data-commons-core:$sdCommonsVersion"
|
||||
|
||||
runtime "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import static org.springframework.data.rest.core.util.UriUtils.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonAnyGetter;
|
||||
import org.springframework.data.rest.repository.AttributeMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class EntityResource extends Resource<Map<String, Object>> {
|
||||
|
||||
public EntityResource(Map<String, Object> dto, Set<Link> links) {
|
||||
super(dto, links);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static EntityResource wrap(Object entity, RepositoryMetadata repoMeta, URI baseUri) {
|
||||
|
||||
Set<Link> links = new HashSet<Link>();
|
||||
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<String, Object> entityDto = new HashMap<String, Object>();
|
||||
for(Map.Entry<String, AttributeMetadata> attrMeta : ((Map<String, AttributeMetadata>)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<String, Object> getContent() {
|
||||
return super.getContent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ConversionService> conversionServices = Arrays.<ConversionService>asList(new DefaultFormattingConversionService());
|
||||
protected List<ConversionService> conversionServices = Arrays.<ConversionService>asList(new DefaultFormattingConversionService());
|
||||
@Autowired(required = false)
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
@Autowired(required = false)
|
||||
protected List<Module> modules = Collections.emptyList();
|
||||
protected List<Module> 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<String>(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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<HttpMessageConverter<?>> customConverters = Collections.emptyList();
|
||||
private Multimap<Class<?>, ResourcePostProcessor> resourcePostProcessors = ArrayListMultimap.create();
|
||||
private List<ResourceSetPostProcessor> resourceSetPostProcessors = Collections.emptyList();
|
||||
private Map<Class<?>, 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<HttpMessageConverter<?>> customConverters = Collections.emptyList();
|
||||
private Map<Class<?>, 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<ResourceSetPostProcessor> 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<ResourceSetPostProcessor> 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<Class<?>, ResourcePostProcessor> postProcessors) {
|
||||
if(null == postProcessors) {
|
||||
return this;
|
||||
}
|
||||
for(Map.Entry<Class<?>, 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<ResourcePostProcessor> getResourcePostProcessors(Class<?> type) {
|
||||
Collection<ResourcePostProcessor> pps = resourcePostProcessors.get(type);
|
||||
if(null == pps) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return pps;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<URI> BASE_URI = new ThreadLocal<URI>();
|
||||
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<Link> links = new ArrayList<Link>();
|
||||
for(RepositoryExporter repoExporter : repositoryExporters) {
|
||||
for(String name : (Set<String>)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<String>(Collections.<String>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<Link> links = new HashSet<Link>();
|
||||
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<Resource<Map<String, Object>>> allResources = new ArrayList<Resource<Map<String, Object>>>();
|
||||
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<Link> links = new HashSet<Link>();
|
||||
|
||||
for(Map.Entry<String, RepositoryQueryMethod> entry : ((Map<String, RepositoryQueryMethod>)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<String>(Collections.<String>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<String>(Collections.<String>emptyList()));
|
||||
}
|
||||
|
||||
ResourceSet resources = new ResourceSet();
|
||||
Set<org.springframework.hateoas.Link> links = new HashSet<org.springframework.hateoas.Link>();
|
||||
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<Object> results = new ArrayList<Object>();
|
||||
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<Link> links = new HashSet<Link>();
|
||||
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<Resource<?>> outgoing = new ArrayList<Resource<?>>();
|
||||
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<String, Object> resource = new HashMap<String, Object>();
|
||||
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>)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<Resource<?>> outgoing = new ArrayList<Resource<?>>();
|
||||
if(shouldReturnLinks(accept)) {
|
||||
Resource<?> resource = new Resource<Object>();
|
||||
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<AttributeMetadata> entityMetadata,
|
||||
URI baseUri) {
|
||||
Map<String, Object> entityDto = new HashMap<String, Object>();
|
||||
MapResource resource = new MapResource(entityDto);
|
||||
|
||||
for(Map.Entry<String, AttributeMetadata> 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<Link> extractLinkedProperties(String repoRel,
|
||||
// Object entity,
|
||||
// EntityMetadata<AttributeMetadata> entityMetadata,
|
||||
// URI baseUri) {
|
||||
// Set<Link> links = new HashSet<Link>();
|
||||
// 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<String, Object> extractEmbeddedProperties(String repoRel,
|
||||
// Object entity,
|
||||
// EntityMetadata<AttributeMetadata> entityMetadata,
|
||||
// URI baseUri) {
|
||||
// Map<String, Object> entityDto = new HashMap<String, Object>();
|
||||
// for(Map.Entry<String, AttributeMetadata> 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) {
|
||||
|
||||
@@ -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.<HandlerMethodReturnValueHandler>asList(
|
||||
resourcesReturnValueHandler
|
||||
));
|
||||
}
|
||||
|
||||
@Override public int getOrder() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Resource> {
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<ResourceSet> {
|
||||
}
|
||||
@@ -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<ResourcesReturnValueHandler>
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Ob
|
||||
|
||||
@Override protected boolean supports(Class<?> 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<Link> links = new HashSet<Link>();
|
||||
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<String>("", links));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,22 +65,15 @@ public class UriListHttpMessageConverter extends AbstractHttpMessageConverter<Ob
|
||||
throws IOException,
|
||||
HttpMessageNotWritableException {
|
||||
OutputStream body = outputMessage.getBody();
|
||||
if(links instanceof LinkList) {
|
||||
for(Link link : ((LinkList)links).getLinks()) {
|
||||
body.write(link.href().toASCIIString().getBytes());
|
||||
body.write('\n');
|
||||
}
|
||||
} else if(links instanceof List) {
|
||||
for(Object o : (List)links) {
|
||||
if(links instanceof Set) {
|
||||
for(Object o : (Set)links) {
|
||||
if(o instanceof Link) {
|
||||
body.write(((Link)o).href().toASCIIString().getBytes());
|
||||
body.write(((Link)o).getHref().getBytes());
|
||||
} else {
|
||||
body.write(o.toString().getBytes());
|
||||
}
|
||||
body.write('\n');
|
||||
}
|
||||
} else if(links instanceof Map) {
|
||||
writeInternal(((Map)links).get("links"), outputMessage);
|
||||
} else if(links instanceof RepositoryMethodResponse) {
|
||||
writeInternal(((RepositoryMethodResponse)links).getLinks(), outputMessage);
|
||||
} else if(links instanceof Resource) {
|
||||
|
||||
@@ -44,8 +44,4 @@ class EventsSpec extends BaseSpec {
|
||||
|
||||
}
|
||||
|
||||
def "captures resource rendering events"() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class QueryMethodsSpec extends BaseSpec {
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.OK
|
||||
body.links.size() == 3
|
||||
body.links.size() == 4
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.data.rest.test.webmvc.ApplicationConfig
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler
|
||||
import org.springframework.hateoas.Link
|
||||
import org.springframework.hateoas.Resource
|
||||
import org.springframework.hateoas.ResourceProcessor
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@ContextConfiguration(classes = [ApplicationConfig, RepositoryRestMvcConfiguration])
|
||||
class ResourceProcessorSpec extends Specification {
|
||||
|
||||
static STRING_RESOURCE_PARAM = new MethodParameter(ResourceProcessorSpec.getMethod("createStringResource"), -1)
|
||||
static LONG_RESOURCE_PARAM = new MethodParameter(ResourceProcessorSpec.getMethod("createLongResource"), -1)
|
||||
static SPECIAL_STRING_RESOURCE_PARAM = new MethodParameter(ResourceProcessorSpec.getMethod("createSpecialStringResource"), -1)
|
||||
static SPECIAL_LONG_RESOURCE_PARAM = new MethodParameter(ResourceProcessorSpec.getMethod("createSpecialLongResource"), -1)
|
||||
|
||||
HandlerMethodReturnValueHandler delegateHandler
|
||||
List<ResourceProcessor<?>> 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<String> createStringResource() {
|
||||
new Resource<String>("string-resource")
|
||||
}
|
||||
|
||||
Resource<Long> createLongResource() {
|
||||
new Resource<Long>(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<Resource<String>> {
|
||||
@Override Resource<String> process(Resource<String> resource) {
|
||||
resource.add(new Link("http://localhost:8080/string-resource", "string-resource"))
|
||||
resource
|
||||
}
|
||||
}
|
||||
|
||||
class LongResourceProcessor implements ResourceProcessor<Resource<Long>> {
|
||||
@Override Resource<Long> process(Resource<Long> resource) {
|
||||
resource.add(new Link("http://localhost:8080/long-resource", "long-resource"))
|
||||
resource
|
||||
}
|
||||
}
|
||||
|
||||
class StringResource extends Resource<String> {
|
||||
StringResource(String content, Link... links) {
|
||||
super(content, links)
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialStringResourceProcessor implements ResourceProcessor<StringResource> {
|
||||
@Override StringResource process(StringResource resource) {
|
||||
resource.add(new Link("http://localhost:8080/special-string-resource", "special-string-resource"))
|
||||
resource
|
||||
}
|
||||
}
|
||||
|
||||
class LongResource extends Resource<Long> {
|
||||
LongResource(Long content, Link... links) {
|
||||
super(content, links)
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialLongResourceProcessor implements ResourceProcessor<LongResource> {
|
||||
@Override LongResource process(LongResource resource) {
|
||||
resource.add(new Link("http://localhost:8080/special-long-resource", "special-long-resource"))
|
||||
resource
|
||||
}
|
||||
}
|
||||
@@ -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<Class<?>, ResourcePostProcessor> resourcePostProcessors() {
|
||||
Map<Class<?>, ResourcePostProcessor> resourcePostProcessors = new HashMap<Class<?>, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Person, Lon
|
||||
@RestResource(path = "nameStartsWith", rel = "nameStartsWith")
|
||||
Page findByNameStartsWith(@Param("name") String name, Pageable p);
|
||||
|
||||
@Query("select count(p) from Person p")
|
||||
@RestResource(path = "count") Long personCount();
|
||||
|
||||
@Query("select p from Person p where p.id in(:ids)")
|
||||
@RestResource(path = "id")
|
||||
Page<Person> findById(@Param("ids") List<Long> ids, Pageable pageable);
|
||||
@RestResource(path = "id") Page<Person> findById(@Param("ids") List<Long> ids, Pageable pageable);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
value="org.springframework.data.rest.test.webmvc.PersonRepository"/>
|
||||
</map>
|
||||
</property>
|
||||
<property name="resourcePostProcessors">
|
||||
<map key-type="java.lang.Class">
|
||||
<entry key="org.springframework.data.rest.test.webmvc.Person">
|
||||
<bean class="org.springframework.data.rest.test.webmvc.LoggingResourcePostProcessor"/>
|
||||
</entry>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!--
|
||||
|
||||
Reference in New Issue
Block a user